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",
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://projects.blender.org/tracker/index.php?"\
34
"category": "Import-Export"}
38
from math import radians
41
from mathutils import Matrix
51
#Container for the exporter settings
52
class MarmaladeExporterSettings:
62
ExportVertexColors=True,
63
ExportMaterialColors=True,
65
CopyTextureFiles=True,
66
ExportArmatures=False,
67
ExportAnimationFrames=0,
68
ExportAnimationActions=0,
72
self.context = context
73
self.FilePath = FilePath
74
self.CoordinateSystem = int(CoordinateSystem)
75
self.FlipNormals = FlipNormals
76
self.ApplyModifiers = ApplyModifiers
78
self.AnimFPS = AnimFPS
79
self.ExportVertexColors = ExportVertexColors
80
self.ExportMaterialColors = ExportMaterialColors
81
self.ExportTextures = ExportTextures
82
self.CopyTextureFiles = CopyTextureFiles
83
self.ExportArmatures = ExportArmatures
84
self.ExportAnimationFrames = int(ExportAnimationFrames)
85
self.ExportAnimationActions = int(ExportAnimationActions)
86
self.ExportMode = int(ExportMode)
87
self.MergeModes = int(MergeModes)
88
self.Verbose = Verbose
92
def ExportMadeWithMarmaladeGroup(Config):
93
print("----------\nExporting to {}".format(Config.FilePath))
95
print("Opening File...")
96
Config.File = open(Config.FilePath, "w")
102
print("writing group header")
104
Config.File.write('// Marmalade group file exported from : %s\n' % bpy.data.filepath)
105
Config.File.write('// Exported %s\n' % str(datetime.datetime.now()))
106
Config.File.write("CIwResGroup\n{\n\tname \"%s\"\n" % bpy.path.display_name_from_filepath(Config.FilePath))
109
print("Generating Object list for export... (Root parents only)")
110
if Config.ExportMode == 1:
111
Config.ExportList = [Object for Object in Config.context.scene.objects
112
if Object.type in {'ARMATURE', 'EMPTY', 'MESH'}
113
and Object.parent is None]
115
ExportList = [Object for Object in Config.context.selected_objects
116
if Object.type in {'ARMATURE', 'EMPTY', 'MESH'}]
117
Config.ExportList = [Object for Object in ExportList
118
if Object.parent not in ExportList]
120
print(" List: {}\nDone".format(Config.ExportList))
123
print("Setting up...")
125
if Config.ExportAnimationFrames:
127
print(bpy.context.scene)
128
print(bpy.context.scene.frame_current)
129
CurrentFrame = bpy.context.scene.frame_current
130
#comment because it crashes Blender on some old blend file: bpy.context.scene.frame_current = bpy.context.scene.frame_current
134
Config.ObjectList = []
136
print("Writing Objects...")
137
WriteObjects(Config, Config.ExportList)
142
print("Objects Exported: {}".format(Config.ExportList))
144
if Config.ExportAnimationFrames:
146
print("Writing Animation...")
147
WriteKeyedAnimationSet(Config, bpy.context.scene)
148
bpy.context.scene.frame_current = CurrentFrame
151
Config.File.write("}\n")
156
def GetObjectChildren(Parent):
157
return [Object for Object in Parent.children
158
if Object.type in {'ARMATURE', 'EMPTY', 'MESH'}]
161
#Returns the file path of first image texture from Material.
162
def GetMaterialTextureFullPath(Config, Material):
164
#Create a list of Textures that have type "IMAGE"
165
ImageTextures = [Material.texture_slots[TextureSlot].texture for TextureSlot in Material.texture_slots.keys() if Material.texture_slots[TextureSlot].texture.type == "IMAGE"]
166
#Refine a new list with only image textures that have a file source
167
TexImages = [Texture.image for Texture in ImageTextures if getattr(Texture.image, "source", "") == "FILE"]
168
ImageFiles = [Texture.image.filepath for Texture in ImageTextures if getattr(Texture.image, "source", "") == "FILE"]
170
filepath = TexImages[0].filepath
171
if TexImages[0].packed_file:
172
TexImages[0].unpack()
173
if not os.path.exists(filepath):
174
#try relative path to the blend file
175
filepath = os.path.dirname(bpy.data.filepath) + filepath
176
#Marmalade doesn't like jpeg/tif so try to convert in png on the fly
177
if (TexImages[0].file_format == 'JPEG' or TexImages[0].file_format == 'TIFF') and os.path.exists(filepath):
178
marmaladeConvert = os.path.expandvars("%S3E_DIR%\\..\\tools\\ImageMagick\\win32\\convert.exe")
179
if (os.path.exists(marmaladeConvert)):
180
srcImagefilepath = filepath
181
filepath = os.path.splitext(filepath)[0] + '.png'
183
print(" /!\\ Converting Texture %s in PNG: %s{}..." % (TexImages[0].file_format, filepath))
184
print('"%s" "%s" "%s"' % (marmaladeConvert, srcImagefilepath, filepath))
185
subprocess.call([marmaladeConvert, srcImagefilepath, filepath])
190
def WriteObjects(Config, ObjectList, geoFile=None, mtlFile=None, GeoModel=None, bChildObjects=False):
191
Config.ObjectList += ObjectList
193
if bChildObjects == False and Config.MergeModes > 0:
195
#we merge objects, so use name of group file for the name of Geo
196
geoFile, mtlFile = CreateGeoMtlFiles(Config, bpy.path.display_name_from_filepath(Config.FilePath))
197
GeoModel = CGeoModel(bpy.path.display_name_from_filepath(Config.FilePath))
199
for Object in ObjectList:
201
print(" Writing Object: {}...".format(Object.name))
203
if Config.ExportArmatures and Object.type == "ARMATURE":
204
Armature = Object.data
205
ParentList = [Bone for Bone in Armature.bones if Bone.parent is None]
207
print(" Writing Armature Bones...")
208
#Create the skel file
209
skelfullname = os.path.dirname(Config.FilePath) + "\models\%s.skel" % (StripName(Object.name))
210
ensure_dir(skelfullname)
212
print(" Creating skel file %s" % (skelfullname))
214
skelFile = open(skelfullname, "w")
215
skelFile.write('// skel file exported from : %r\n' % os.path.basename(bpy.data.filepath))
216
skelFile.write("CIwAnimSkel\n")
217
skelFile.write("{\n")
218
skelFile.write("\tnumBones %d\n" % (len(Armature.bones)))
219
Config.File.write("\t\".\models\%s.skel\"\n" % (StripName(Object.name)))
221
WriteArmatureParentRootBones(Config, Object, ParentList, skelFile)
223
skelFile.write("}\n")
228
ChildList = GetObjectChildren(Object)
229
if Config.ExportMode == 2: # Selected Objects Only
230
ChildList = [Child for Child in ChildList
231
if Child in Config.context.selected_objects]
233
print(" Writing Children...")
234
WriteObjects(Config, ChildList, geoFile, mtlFile, GeoModel, True)
236
print(" Done Writing Children")
238
if Object.type == "MESH":
240
print(" Generating Mesh...")
241
if Config.ApplyModifiers:
242
if Config.ExportArmatures:
243
#Create a copy of the object and remove all armature modifiers so an unshaped
244
#mesh can be created from it.
245
Object2 = Object.copy()
246
for Modifier in [Modifier for Modifier in Object2.modifiers if Modifier.type == "ARMATURE"]:
247
Object2.modifiers.remove(Modifier)
248
Mesh = Object2.to_mesh(bpy.context.scene, True, "PREVIEW")
250
Mesh = Object.to_mesh(bpy.context.scene, True, "PREVIEW")
252
Mesh = Object.to_mesh(bpy.context.scene, False, "PREVIEW")
255
print(" Writing Mesh...")
257
# Flip ZY axis (Blender Z up: Marmalade: Y up) ans Scale appropriately
258
X_ROT = mathutils.Matrix.Rotation(-math.pi / 2, 4, 'X')
260
if Config.MergeModes == 0:
261
# No merge, so all objects are exported in MODEL SPACE and not in world space
262
# Calculate Scale of the Export
263
meshScale = Object.matrix_world.to_scale() # Export is working, even if user doesn't have use apply scale in Edit mode.
265
scalematrix = Matrix()
266
scalematrix[0][0] = meshScale.x * Config.Scale
267
scalematrix[1][1] = meshScale.y * Config.Scale
268
scalematrix[2][2] = meshScale.z * Config.Scale
270
meshRot = Object.matrix_world.to_quaternion() # Export is working, even if user doesn't have use apply Rotation in Edit mode.
271
Mesh.transform(X_ROT * meshRot.to_matrix().to_4x4() * scalematrix)
273
# In Merge mode, we need to keep relative postion of each objects, so we export in WORLD SPACE
274
SCALE_MAT = mathutils.Matrix.Scale(Config.Scale, 4)
275
Mesh.transform(SCALE_MAT * X_ROT * Object.matrix_world)
277
# manage merge options
279
if Config.MergeModes == 0:
280
#one geo per Object, so use name of Object for the Geo file
281
geoFile, mtlFile = CreateGeoMtlFiles(Config, StripName(Object.name))
282
GeoModel = CGeoModel(StripName(Object.name))
284
# Write the Mesh in the Geo file
285
WriteMesh(Config, Object, Mesh, geoFile, mtlFile, GeoModel)
287
if Config.MergeModes == 0:
288
# no merge so finalize the file, and discard the file and geo class
289
FinalizeGeoMtlFiles(Config, geoFile, mtlFile)
293
elif Config.MergeModes == 1:
294
# merge in one Mesh, so keep the Geo class and prepare to change object
296
elif Config.MergeModes == 2:
297
# merge several Meshes in one file: so clear the mesh data that we just written in the file,
298
# but keep Materials info that need to be merged across objects
299
GeoModel.ClearAllExceptMaterials()
304
if Config.ApplyModifiers and Config.ExportArmatures:
305
bpy.data.objects.remove(Object2)
306
bpy.data.meshes.remove(Mesh)
309
print(" Done Writing Object: {}".format(Object.name))
311
if bChildObjects == False:
312
# we have finish to do all objects
314
if Config.MergeModes == 1:
315
# we have Merges all objects in one Mesh, so time to write this big mesh in the file
316
GeoModel.PrintGeoMesh(geoFile)
317
# time to write skinfile if any
318
if len(GeoModel.useBonesDict) > 0:
319
# some mesh was not modified by the armature. so we must skinned the merged mesh.
320
# So unskinned vertices from unarmatured meshes, are assigned to the root bone of the armature
321
for i in range(0, len(GeoModel.vList)):
322
if not i in GeoModel.skinnedVertices:
323
GeoModel.skinnedVertices.append(i)
324
useBonesKey = pow(2, GeoModel.armatureRootBoneIndex)
325
vertexGroupIndices = list((GeoModel.armatureRootBoneIndex,))
326
if useBonesKey not in GeoModel.useBonesDict:
327
GeoModel.mapVertexGroupNames[GeoModel.armatureRootBoneIndex] = StripBoneName(GeoModel.armatureRootBone.name)
329
VertexList.append("\t\tvertWeights { %d, 1.0}" % i)
330
GeoModel.useBonesDict[useBonesKey] = (vertexGroupIndices, VertexList)
332
pair_ListGroupIndices_ListAssignedVertices = GeoModel.useBonesDict[useBonesKey]
333
pair_ListGroupIndices_ListAssignedVertices[1].append("\t\tvertWeights { %d, 1.0}" % i)
334
GeoModel.useBonesDict[useBonesKey] = pair_ListGroupIndices_ListAssignedVertices
335
# now generates the skin file
336
PrintSkinWeights(Config, GeoModel.armatureObjectName, GeoModel.useBonesDict, GeoModel.mapVertexGroupNames, GeoModel.name)
337
if Config.MergeModes > 0:
338
WriteMeshMaterialsForGeoModel(Config, mtlFile, GeoModel)
339
FinalizeGeoMtlFiles(Config, geoFile, mtlFile)
345
def CreateGeoMtlFiles(Config, Name):
347
geofullname = os.path.dirname(Config.FilePath) + ("\models\%s.geo" % Name)
348
ensure_dir(geofullname)
350
print(" Creating geo file %s" % (geofullname))
351
geoFile = open(geofullname, "w")
352
geoFile.write('// geo file exported from : %r\n' % os.path.basename(bpy.data.filepath))
353
geoFile.write("CIwModel\n")
355
geoFile.write("\tname \"%s\"\n" % Name)
356
# add it to the group
357
Config.File.write("\t\".\models\%s.geo\"\n" % Name)
359
# Create the mtl file
360
mtlfullname = os.path.dirname(Config.FilePath) + "\models\%s.mtl" % (Name)
361
ensure_dir(mtlfullname)
363
print(" Creating mtl file %s" % (mtlfullname))
364
mtlFile = open(mtlfullname, "w")
365
mtlFile.write('// mtl file exported from : %r\n' % os.path.basename(bpy.data.filepath))
366
return geoFile, mtlFile
369
def FinalizeGeoMtlFiles(Config, geoFile, mtlFile):
371
print(" Closing geo file")
375
print(" Closing mtl file")
379
def WriteMesh(Config, Object, Mesh, geoFile=None, mtlFile=None, GeoModel=None):
380
if geoFile == None or mtlFile == None:
381
print (" ERROR not geo file arguments in WriteMesh method")
385
print (" ERROR not GeoModel arguments in WriteMesh method")
388
BuildOptimizedGeo(Config, Object, Mesh, GeoModel)
389
if Config.MergeModes == 0 or Config.MergeModes == 2:
390
#if we don't merge, or if we write several meshes into one file ... write the mesh everytime we do an object
391
GeoModel.PrintGeoMesh(geoFile)
394
print(" Done\n Writing Mesh Materials...")
396
if Config.MergeModes == 0:
397
#No merge, so we can diretly write the Mtl file associated to this object
398
WriteMeshMaterialsForGeoModel(Config, mtlFile, GeoModel)
403
if Config.ExportArmatures:
405
print(" Writing Mesh Weights...")
406
WriteMeshSkinWeightsForGeoModel(Config, Object, Mesh, GeoModel)
411
###### optimized version fo Export, can be used also to merge several object in one single geo File ######
418
# -> List Vertex Colors
421
# -> Blender Material Object
422
# -> List Tris -> Stream Indices v,vn,uv0,uv1,vc
423
# -> List Quads -> Stream Indices v,vn,uv0,uv1,vc
427
#Store one Point of a Quad or Tri in marmalade geo format: //index-list is: { <int> <int> <int> <int> <int> } //v,vn,uv0,uv1,vc
430
__slots__ = "v", "vn", "uv0", "uv1", "vc"
432
def __init__(self, v, vn, uv0, uv1, vc):
441
#Store a Quad or a Tri in marmalade geo format : 3 or 4 CIndexList depending it is a Tri or a Quad
444
__slots__ = "pointsList",
449
def AddPoint(self, v, vn, uv0, uv1, vc):
450
self.pointsList.append( CGeoIndexList(v, vn, uv0, uv1, vc))
452
def PointsCount(self):
453
return len(self.pointsList)
455
def PrintPoly(self, geoFile):
456
if len(self.pointsList) == 3:
457
geoFile.write("\t\t\t\tt ")
458
if len(self.pointsList) == 4:
459
geoFile.write("\t\t\t\tq ")
460
for point in self.pointsList:
461
geoFile.write(" {%d, %d, %d, %d, %d}" % (point.v, point.vn, point.uv0, point.uv1, point.vc))
466
#Store all the poly (tri or quad) assigned to a Material in marmalade geo format
468
class CGeoMaterialPolys:
469
__slots__ = "name", "material", "quadList", "triList", "currentPoly"
471
def __init__(self, name, material=None):
473
self.material = material
476
self.currentPoly = None
479
self.currentPoly = CGeoPoly()
481
def AddPoint(self, v, vn, uv0, uv1, vc):
482
self.currentPoly.AddPoint(v, vn, uv0, uv1, vc)
485
if (self.currentPoly.PointsCount() == 3):
486
self.triList.append(self.currentPoly)
487
if (self.currentPoly.PointsCount() == 4):
488
self.quadList.append(self.currentPoly)
489
self.currentPoly = None
491
def ClearPolys(self):
494
self.currentPoly = None
496
def PrintMaterialPolys(self, geoFile):
497
geoFile.write("\t\tCSurface\n")
498
geoFile.write("\t\t{\n")
499
geoFile.write("\t\t\tmaterial \"%s\"\n" % self.name)
501
geoFile.write("\t\t\tCTris\n")
502
geoFile.write("\t\t\t{\n")
503
geoFile.write("\t\t\t\tnumTris %d\n" % (len(self.triList)))
504
for poly in self.triList:
505
poly.PrintPoly(geoFile)
506
geoFile.write("\t\t\t}\n")
509
geoFile.write("\t\t\tCQuads\n")
510
geoFile.write("\t\t\t{\n")
511
geoFile.write("\t\t\t\tnumQuads %d\n" % (len(self.quadList)))
512
for poly in self.quadList:
513
poly.PrintPoly(geoFile)
514
geoFile.write("\t\t\t}\n")
515
geoFile.write("\t\t}\n")
519
#Store all the information on a Model/Mesh (vertices, normal, certcies color, uv0, uv1, TRI, QUAD) in marmalade geo format
522
__slots__ = ("name", "MaterialsDict", "vList", "vnList", "vcList", "uv0List", "uv1List",
523
"currentMaterialPolys", "vbaseIndex","vnbaseIndex", "uv0baseIndex", "uv1baseIndex",
524
"armatureObjectName", "useBonesDict", "mapVertexGroupNames", "armatureRootBone", "armatureRootBoneIndex", "skinnedVertices")
526
def __init__(self, name):
528
self.MaterialsDict = {}
534
self.currentMaterialPolys = None
535
#used xx baseIndex are used when merging several blender objects into one Mesh in the geo file (internal offset)
538
self.uv0baseIndex = 0
539
self.uv1baseIndex = 0
541
# Store some information for skin management , when we merge several object in one big mesh (MergeModes 1)
542
# can only work if in the object list only one is rigged with an armature... and if it is located in 0,0,0
543
self.armatureObjectName = ""
544
#useBonesKey : bit field, where each bit is a VertexGroup.Index): Sum(2^VertGroupIndex).
545
#useBonesDict[useBonesKey] = tuple(VertexGroups.group, list(Vertex))
546
self.useBonesDict = {}
547
self.mapVertexGroupNames = {}
548
self.armatureRootBone = None
549
self.armatureRootBoneIndex = 0
550
self.skinnedVertices = []
554
def AddVertex(self, vertex):
555
self.vList.append(vertex.copy())
557
def AddVertexNormal(self, vertexN):
558
self.vnList.append(vertexN.copy())
560
# add a uv coordiantes and return the current Index in the stream (index is local to the object, when we merge several object into a one Mesh)
561
def AddVertexUV0(self, u, v):
562
self.uv0List.append((u, v))
563
return len(self.uv0List) - 1 - self.uv0baseIndex
565
def AddVertexUV1(self, u, v):
566
self.uv1List.append((u, v))
567
return len(self.uv1List) - 1 - self.uv1baseIndex
569
# add a vertexcolor if it doesn't already exist and return the current Index in the stream (index is global to all objects, when we merge several object into a one Mesh)
570
def AddVertexColor(self, r, g, b, a):
571
for i in range(0, len(self.vcList)):
573
if col[0] == r and col[1] == g and col[2] == b and col[3] == a:
576
self.vcList.append((r, g, b, a))
577
return len(self.vcList)-1
579
def BeginPoly(self, MaterialName, material=None):
580
if MaterialName not in self.MaterialsDict:
581
self.currentMaterialPolys = CGeoMaterialPolys(MaterialName, material)
583
self.currentMaterialPolys = self.MaterialsDict[MaterialName]
584
self.currentMaterialPolys.BeginPoly()
586
def AddPoint(self, v, vn, uv0, uv1, vc):
590
vn += self.vnbaseIndex
592
uv0 += self.uv0baseIndex
594
uv1 += self.uv1baseIndex
596
self.currentMaterialPolys.AddPoint(v, vn, uv0, uv1, vc)
599
self.currentMaterialPolys.EndPoly()
600
self.MaterialsDict[self.currentMaterialPolys.name] = self.currentMaterialPolys
601
self.currentMaterialPolys = None
604
#used in Merge mode 1: allows to merge several blender objects into one Mesh.
605
self.vbaseIndex = len(self.vList)
606
self.vnbaseIndex = len(self.vnList)
607
self.uv0baseIndex = len(self.uv0List)
608
self.uv1baseIndex = len(self.uv1List)
610
def ClearAllExceptMaterials(self):
611
#used in Merge mode 2: one geo with several mesh
617
self.currentMaterialPolys = None
620
self.uv0baseIndex = 0
621
self.uv1baseIndex = 0
622
for GeoMaterialPolys in self.MaterialsDict.values():
623
GeoMaterialPolys.ClearPolys()
624
self.useBonesDict = {}
625
self.mapVertexGroupNames = {}
626
self.armatureObjectName = ""
627
self.armatureRootBone = None
628
self.armatureRootBoneIndex = 0
629
self.skinnedVertices = []
631
def PrintGeoMesh(self, geoFile):
632
geoFile.write("\tCMesh\n")
633
geoFile.write("\t{\n")
634
geoFile.write("\t\tname \"%s\"\n" % (StripName(self.name)))
637
geoFile.write("\t\tCVerts\n")
638
geoFile.write("\t\t{\n")
639
geoFile.write("\t\t\tnumVerts %d\n" % len(self.vList))
640
for vertex in self.vList:
641
geoFile.write("\t\t\tv { %.9f, %.9f, %.9f }\n" % (vertex[0], vertex[1], vertex[2]))
642
geoFile.write("\t\t}\n")
645
geoFile.write("\t\tCVertNorms\n")
646
geoFile.write("\t\t{\n")
647
geoFile.write("\t\t\tnumVertNorms %d\n" % len(self.vnList))
648
for vertexn in self.vnList:
649
geoFile.write("\t\t\tvn { %.9f, %.9f, %.9f }\n" % (vertexn[0], vertexn[1], vertexn[2]))
650
geoFile.write("\t\t}\n")
653
geoFile.write("\t\tCVertCols\n")
654
geoFile.write("\t\t{\n")
655
geoFile.write("\t\t\tnumVertCols %d\n" % len(self.vcList))
656
for color in self.vcList:
657
geoFile.write("\t\t\tcol { %.6f, %.6f, %.6f, %.6f }\n" % (color[0], color[1], color[2], color[3])) #alpha is not supported on blender for vertex colors
658
geoFile.write("\t\t}\n")
661
geoFile.write("\t\tCUVs\n")
662
geoFile.write("\t\t{\n")
663
geoFile.write("\t\t\tsetID 0\n")
664
geoFile.write("\t\t\tnumUVs %d\n" % len(self.uv0List))
665
for uv in self.uv0List:
666
geoFile.write("\t\t\tuv { %.9f, %.9f }\n" % (uv[0], uv[1]))
667
geoFile.write("\t\t}\n")
670
geoFile.write("\t\tCUVs\n")
671
geoFile.write("\t\t{\n")
672
geoFile.write("\t\t\tsetID 1\n")
673
geoFile.write("\t\t\tnumUVs %d\n" % len(self.uv1List))
674
for uv in self.uv1List:
675
geoFile.write("\t\t\tuv { %.9f, %.9f }\n" % (uv[0], uv[1]))
676
geoFile.write("\t\t}\n")
678
for GeoMaterialPolys in self.MaterialsDict.values():
679
GeoMaterialPolys.PrintMaterialPolys(geoFile)
680
geoFile.write("\t}\n")
682
def GetMaterialList(self):
683
return list(self.MaterialsDict.keys())
685
def GetMaterialByName(self, name):
686
if name in self.MaterialsDict:
687
return self.MaterialsDict[name].material
694
# iterates faces, vertices ... and store the information in the GeoModel container
695
def BuildOptimizedGeo(Config, Object, Mesh, GeoModel):
697
GeoModel = CGeoModel(filename, Object.name)
699
#Ensure tessfaces data are here
700
Mesh.update (calc_tessface=True)
702
#Store Vertex stream, and Normal stream (use directly the order from blender collection
703
for Vertex in Mesh.vertices:
704
GeoModel.AddVertex(Vertex.co)
705
Normal = Vertex.normal
706
if Config.FlipNormals:
708
GeoModel.AddVertexNormal(Normal)
709
#Check if some colors have been defined
711
if Config.ExportVertexColors and (len(Mesh.vertex_colors) > 0):
712
vertexColours = Mesh.tessface_vertex_colors[0].data
714
#Check if some uv coordinates have been defined
716
if Config.ExportTextures and (len(Mesh.uv_textures) > 0):
717
for UV in Mesh.tessface_uv_textures:
719
UVCoordinates = UV.data
722
#Iterate on Faces and Store the poly (quad or tri) and the associate colors,UVs
723
for Face in Mesh.tessfaces:
724
# stream for vertex (we use the same for normal)
725
Vertices = list(Face.vertices)
726
if Config.CoordinateSystem == 1:
727
Vertices = Vertices[::-1]
728
# stream for vertex colors
730
MeshColor = vertexColours[Face.index]
731
if len(Vertices) == 3:
732
FaceColors = list((MeshColor.color1, MeshColor.color2, MeshColor.color3))
734
FaceColors = list((MeshColor.color1, MeshColor.color2, MeshColor.color3, MeshColor.color4))
735
if Config.CoordinateSystem == 1:
736
FaceColors = FaceColors[::-1]
738
for color in FaceColors:
739
index = GeoModel.AddVertexColor(color[0], color[1], color[2], 1) #rgba => no alpha on vertex color in Blender so use 1
740
colorIndex.append(index)
742
colorIndex = list((-1,-1,-1,-1))
744
# stream for UV0 coordinates
746
uvFace = UVCoordinates[Face.index]
748
for uvVertex in uvFace.uv:
749
uvVertices.append(tuple(uvVertex))
750
if Config.CoordinateSystem == 1:
751
uvVertices = uvVertices[::-1]
753
for uvVertex in uvVertices:
754
index = GeoModel.AddVertexUV0(uvVertex[0], 1 - uvVertex[1])
755
uv0Index.append(index)
757
uv0Index = list((-1, -1, -1, -1))
759
# stream for UV1 coordinates
760
uv1Index = list((-1, -1, -1, -1))
763
# find the associated material
764
if Face.material_index < len(Mesh.materials):
765
mat = Mesh.materials[Face.material_index]
769
matName = "NoMaterialAssigned" # There is no material assigned in blender !!!, exporter have generated a default one
771
# now on the material, generates the tri/quad in v,vn,uv0,uv1,vc stream index
772
GeoModel.BeginPoly(matName, mat)
774
for i in range(0, len(Vertices)):
775
GeoModel.AddPoint(Vertices[i], Vertices[i], uv0Index[i], uv1Index[i], colorIndex[i])
782
# Get the list of Material in use by the CGeoModel
783
def WriteMeshMaterialsForGeoModel(Config, mtlFile, GeoModel):
784
for matName in GeoModel.GetMaterialList():
785
Material = GeoModel.GetMaterialByName(matName)
786
WriteMaterial(Config, mtlFile, Material)
789
def WriteMaterial(Config, mtlFile, Material=None):
790
mtlFile.write("CIwMaterial\n")
793
mtlFile.write("\tname \"%s\"\n" % Material.name)
795
if Config.ExportMaterialColors:
796
#if bpy.context.scene.world:
797
# MatAmbientColor = Material.ambient * bpy.context.scene.world.ambient_color
798
MatAmbientColor = Material.ambient * Material.diffuse_color
799
mtlFile.write("\tcolAmbient {%.2f,%.2f,%.2f,%.2f} \n" % (min(255, MatAmbientColor[0] * 255), min(255, MatAmbientColor[1] * 255), min(255, MatAmbientColor[2] * 255), min(255, Material.alpha * 255)))
800
MatDiffuseColor = 255 * Material.diffuse_intensity * Material.diffuse_color
801
MatDiffuseColor = min((255, 255, 255)[:],MatDiffuseColor[:])
802
mtlFile.write("\tcolDiffuse {%.2f,%.2f,%.2f} \n" % (MatDiffuseColor[:]))
803
MatSpecularColor = 255 * Material.specular_intensity * Material.specular_color
804
MatSpecularColor = min((255, 255, 255)[:],MatSpecularColor[:])
805
mtlFile.write("\tcolSpecular {%.2f,%.2f,%.2f} \n" % (MatSpecularColor[:]))
806
# EmitColor = Material.emit * Material.diffuse_color
807
# mtlFile.write("\tcolEmissive {%.2f,%.2f,%.2f} \n" % (EmitColor* 255)[:])
809
mtlFile.write("\tname \"NoMaterialAssigned\" // There is no material assigned in blender !!!, exporter have generated a default one\n")
812
if Config.ExportTextures:
813
Texture = GetMaterialTextureFullPath(Config, Material)
815
mtlFile.write("\ttexture0 .\\textures\\%s\n" % (bpy.path.basename(Texture)))
817
if Config.CopyTextureFiles:
818
if not os.path.exists(Texture):
819
#try relative path to the blend file
820
Texture = os.path.dirname(bpy.data.filepath) + Texture
821
if os.path.exists(Texture):
822
textureDest = os.path.dirname(Config.FilePath) + "\\models\\textures\\%s" % (bpy.path.basename(Texture))
823
ensure_dir(textureDest)
825
print(" Copying the texture file %s ---> %s" % (Texture, textureDest))
826
shutil.copy(Texture, textureDest)
829
print(" CANNOT Copy texture file (not found) %s" % (Texture))
832
def GetFirstRootBone(ArmatureObject):
833
ArmatureBones = ArmatureObject.data.bones
834
ParentBoneList = [Bone for Bone in ArmatureBones if Bone.parent is None]
836
return ParentBoneList[0]
840
def GetVertexGroupFromBone(Object, Bone):
842
vertexGroupList = [VertexGroup for VertexGroup in Object.vertex_groups if VertexGroup.name == Bone.name]
844
return vertexGroupList[0]
848
def GetBoneListNames(Bones):
851
boneList.append(Bone.name)
852
boneList += GetBoneListNames(Bone.children)
856
def FindUniqueIndexForRootBone(Object, RootVertexGroup):
858
return RootVertexGroup.index
860
#If there is not VertexGroup associated to the root bone name, we don't have a vertex index.
861
#so use the next available free index
862
return len(Object.vertex_groups)
865
def WriteMeshSkinWeightsForGeoModel(Config, Object, Mesh, GeoModel):
866
ArmatureList = [Modifier for Modifier in Object.modifiers if Modifier.type == "ARMATURE"]
868
ArmatureObject = ArmatureList[0].object
869
if ArmatureObject is None:
871
RootBone = GetFirstRootBone(ArmatureObject)
872
RootVertexGroup = GetVertexGroupFromBone(Object, RootBone)
873
BoneNames = GetBoneListNames(ArmatureObject.data.bones)
875
GeoModel.armatureObjectName = StripName(ArmatureObject.name)
877
GeoModel.armatureRootBone = RootBone
878
GeoModel.armatureRootBoneIndex = FindUniqueIndexForRootBone(Object, RootVertexGroup)
880
# Marmalade need to declare a vertex per list of affected bones
881
# so first we have to get all the combinations of affected bones that exist in the mesh
882
# to build thoses groups, we build a unique key (like a bit field, where each bit is a VertexGroup.Index): Sum(2^VertGroupIndex)... so we have a unique Number per combinations
884
for Vertex in Mesh.vertices:
885
VertexIndex = Vertex.index + GeoModel.vbaseIndex
886
AddVertexToDicionarySkinWeights(Config, Object, Mesh, Vertex, GeoModel.useBonesDict, GeoModel.mapVertexGroupNames, VertexIndex, RootBone, RootVertexGroup, BoneNames)
887
GeoModel.skinnedVertices.append(VertexIndex)
889
if Config.MergeModes != 1:
890
# write skin file directly
891
PrintSkinWeights(Config, GeoModel.armatureObjectName, GeoModel.useBonesDict, GeoModel.mapVertexGroupNames, StripName(Object.name))
894
def PrintSkinWeights(Config, ArmatureObjectName, useBonesDict, mapVertexGroupNames, GeoName):
895
#Create the skin file
896
skinfullname = os.path.dirname(Config.FilePath) + "\models\%s.skin" % GeoName
897
ensure_dir(skinfullname)
899
print(" Creating skin file %s" % (skinfullname))
900
skinFile = open(skinfullname, "w")
901
skinFile.write('// skin file exported from : %r\n' % os.path.basename(bpy.data.filepath))
902
skinFile.write("CIwAnimSkin\n")
903
skinFile.write("{\n")
904
skinFile.write("\tskeleton \"%s\"\n" % ArmatureObjectName)
905
skinFile.write("\tmodel \"%s\"\n" % GeoName)
907
# now we have Bones grouped in the dictionary , along with the associated influenced vertex weighting
908
# So simply iterate the dictionary
909
Config.File.write("\t\".\models\%s.skin\"\n" % GeoName)
910
for pair_ListGroupIndices_ListAssignedVertices in useBonesDict.values():
911
skinFile.write("\tCIwAnimSkinSet\n")
912
skinFile.write("\t{\n")
913
skinFile.write("\t\tuseBones {")
914
for vertexGroupIndex in pair_ListGroupIndices_ListAssignedVertices[0]:
915
skinFile.write(" %s" % mapVertexGroupNames[vertexGroupIndex])
916
skinFile.write(" }\n")
917
skinFile.write("\t\tnumVerts %d\n" % len(pair_ListGroupIndices_ListAssignedVertices[1]))
918
for VertexWeightString in pair_ListGroupIndices_ListAssignedVertices[1]:
919
skinFile.write(VertexWeightString)
920
skinFile.write("\t}\n")
922
skinFile.write("}\n")
926
def AddVertexToDicionarySkinWeights(Config, Object, Mesh, Vertex, useBonesDict, mapVertexGroupNames, VertexIndex, RootBone, RootVertexGroup, BoneNames):
929
vertexGroupIndices = []
931
if (len(Vertex.groups)) > 4:
932
print ("ERROR Vertex %d is influenced by more than 4 bones\n" % (VertexIndex))
933
for VertexGroup in Vertex.groups:
934
if (VertexGroup.weight > 0):
935
groupName = Object.vertex_groups[VertexGroup.group].name
936
if groupName in BoneNames:
937
mapVertexGroupNames[VertexGroup.group] = StripBoneName(groupName)
938
if (len(vertexGroupIndices))<4: #ignore if more 4 bones are influencing the vertex
939
useBonesKey = useBonesKey + pow(2, VertexGroup.group)
940
vertexGroupIndices.append(VertexGroup.group)
941
weightTotal = weightTotal + VertexGroup.weight
942
if (weightTotal == 0):
943
bWeightTotZero = True #avoid divide by zero later on
946
print(" Warning Weight is ZERO for vertex %d => Add it to the root bone" % (VertexIndex))
947
RootBoneGroupIndex = FindUniqueIndexForRootBone(Object, RootVertexGroup)
948
mapVertexGroupNames[RootBoneGroupIndex] = StripBoneName(RootBone.name)
949
useBonesKey = pow(2, RootBoneGroupIndex)
950
vertexGroupIndices = list((RootBoneGroupIndex,))
954
bWeightTotZero = False
956
if len(vertexGroupIndices) > 0:
957
vertexGroupIndices.sort();
959
#build the vertex weight string: vertex indices, followed by influence weight for each bone
960
VertexWeightString = "\t\tvertWeights { %d" % (VertexIndex)
961
for vertexGroupIndex in vertexGroupIndices:
962
#get the weight of this specific VertexGroup (aka bone)
964
for VertexGroup in Vertex.groups:
965
if VertexGroup.group == vertexGroupIndex:
966
boneWeight = VertexGroup.weight
967
#calculate the influence of this bone compared to the total of weighting applied to this Vertex
968
if not bWeightTotZero:
969
VertexWeightString += ", %.7f" % (boneWeight / weightTotal)
971
VertexWeightString += ", %.7f" % (1.0 / len(vertexGroupIndices))
972
VertexWeightString += "}"
974
VertexWeightString += " // total weight was zero in blender , export assign it to the RootBone with weight 1."
975
if (len(Vertex.groups)) > 4:
976
VertexWeightString += " // vertex is associated to more than 4 bones in blender !! skip some bone association (was associated to %d bones)." % (len(Vertex.groups))
977
VertexWeightString += "\n"
979
#store in dictionnary information
980
if useBonesKey not in useBonesDict:
982
VertexList.append(VertexWeightString)
983
useBonesDict[useBonesKey] = (vertexGroupIndices, VertexList)
985
pair_ListGroupIndices_ListAssignedVertices = useBonesDict[useBonesKey]
986
pair_ListGroupIndices_ListAssignedVertices[1].append(VertexWeightString)
987
useBonesDict[useBonesKey] = pair_ListGroupIndices_ListAssignedVertices
989
print ("ERROR Vertex %d is not skinned (it doesn't belong to any vertex group\n" % (VertexIndex))
993
############# ARMATURE: Bone export, and Bone animation export
996
def WriteArmatureParentRootBones(Config, Object, RootBonesList, skelFile):
998
if len(RootBonesList) > 1:
999
print(" /!\\ WARNING ,Marmelade need only one ROOT bone per armature, there is %d root bones " % len(RootBonesList))
1000
print(RootBonesList)
1002
PoseBones = Object.pose.bones
1003
for Bone in RootBonesList:
1005
print(" Writing Root Bone: {}...".format(Bone.name))
1007
PoseBone = PoseBones[Bone.name]
1008
WriteBonePosition(Config, Object, Bone, PoseBones, PoseBone, skelFile, True)
1011
WriteArmatureChildBones(Config, Object, Bone.children, skelFile)
1014
def WriteArmatureChildBones(Config, Object, BonesList, skelFile):
1015
PoseBones = Object.pose.bones
1016
for Bone in BonesList:
1018
print(" Writing Child Bone: {}...".format(Bone.name))
1019
PoseBone = PoseBones[Bone.name]
1020
WriteBonePosition(Config, Object, Bone, PoseBones, PoseBone, skelFile, True)
1024
WriteArmatureChildBones(Config, Object, Bone.children, skelFile)
1027
def WriteBonePosition(Config, Object, Bone, PoseBones, PoseBone, File, isRestPoseNotAnimPose):
1028
# Compute armature scale :
1029
# Many others exporter require sthe user to do Apply Scale in Object Mode to have 1,1,1 scale and so that anim data are correctly scaled
1030
# Here we retreive the Scale of the Armture Object.matrix_world.to_scale() and we use it to scale the bones :-)
1031
# So new Blender user should not complain about bad animation export if they forgot to apply the Scale to 1,1,1
1033
armScale = Object.matrix_world.to_scale()
1034
armRot = Object.matrix_world.to_quaternion()
1035
if isRestPoseNotAnimPose:
1036
#skel file, bone header
1037
File.write("\tCIwAnimBone\n")
1039
File.write("\t\tname \"%s\"\n" % StripBoneName(Bone.name))
1040
#get bone local matrix for rest pose
1042
File.write("\t\tparent \"%s\"\n" % StripBoneName(Bone.parent.name))
1043
localmat = Bone.parent.matrix_local.inverted() * Bone.matrix_local
1045
localmat = Bone.matrix_local
1047
#anim file, bone header
1048
File.write("\t\t\n")
1049
File.write("\t\tbone \"%s\" \n" % StripBoneName(Bone.name))
1050
localmat = PoseBone.matrix
1051
#get bone local matrix for current anim pose
1053
ParentPoseBone = PoseBones[Bone.parent.name]
1054
localmat = ParentPoseBone.matrix.inverted() * PoseBone.matrix
1056
localmat = PoseBone.matrix
1059
#Flip Y Z axes (only on root bone, other bones are local to root bones, so no need to rotate)
1060
X_ROT = mathutils.Matrix.Rotation(-math.pi / 2, 4, 'X')
1061
if Config.MergeModes > 0:
1062
# Merge mode is in world coordinates and not in model coordinates: so apply the world coordinate on the rootbone
1063
localmat = X_ROT * Object.matrix_world * localmat
1064
armScale.x = armScale.y = armScale.z = 1
1066
localmat= X_ROT * armRot.to_matrix().to_4x4() * localmat #apply the armature rotation on the root bone
1069
loc = localmat.to_translation()
1070
quat = localmat.to_quaternion()
1073
loc.x *= (armScale.x * Config.Scale)
1074
loc.y *= (armScale.y * Config.Scale)
1075
loc.z *= (armScale.z * Config.Scale)
1077
File.write("\t\tpos { %.9f, %.9f, %.9f }\n" % (loc[0], loc[1], loc[2]))
1078
File.write("\t\trot { %.9f, %.9f, %.9f, %.9f }\n" % (quat.w, quat.x, quat.y, quat.z))
1080
if isRestPoseNotAnimPose:
1084
def WriteKeyedAnimationSet(Config, Scene):
1085
for Object in [Object for Object in Config.ObjectList if Object.animation_data]:
1087
print(" Writing Animation Data for Object: {}".format(Object.name))
1089
if Config.ExportAnimationActions == 0 and Object.animation_data.action:
1090
actions.append(Object.animation_data.action)
1092
actions = bpy.data.actions[:]
1093
DefaultAction = Object.animation_data.action
1095
for Action in actions:
1096
if Config.ExportAnimationActions == 0:
1097
animFileName = StripName(Object.name)
1099
Object.animation_data.action = Action
1100
animFileName = "%s_%s" % (StripName(Object.name),StripName(Action.name))
1102
#Object animated (aka single bone object)
1103
#build key frame time list
1105
keyframeTimes = set()
1106
if Config.ExportAnimationFrames == 1:
1107
# Exports only key frames
1108
for FCurve in Action.fcurves:
1109
for Keyframe in FCurve.keyframe_points:
1110
if Keyframe.co[0] < Scene.frame_start:
1111
keyframeTimes.add(Scene.frame_start)
1112
elif Keyframe.co[0] > Scene.frame_end:
1113
keyframeTimes.add(Scene.frame_end)
1115
keyframeTimes.add(int(Keyframe.co[0]))
1117
# Exports all frames
1118
keyframeTimes.update(range(Scene.frame_start, Scene.frame_end + 1, 1))
1119
keyframeTimes = list(keyframeTimes)
1120
keyframeTimes.sort()
1121
if len(keyframeTimes):
1122
#Create the anim file for offset animation (or single bone animation
1123
animfullname = os.path.dirname(Config.FilePath) + "\\anims\\%s_offset.anim" % animFileName
1126
## ensure_dir(animfullname)
1127
## if Config.Verbose:
1128
## print(" Creating anim file (single bone animation) %s" % (animfullname))
1129
## animFile = open(animfullname, "w")
1130
## animFile.write('// anim file exported from : %r\n' % os.path.basename(bpy.data.filepath))
1131
## animFile.write("CIwAnim\n")
1132
## animFile.write("{\n")
1133
## animFile.write("\tent \"%s\"\n" % (StripName(Object.name)))
1134
## animFile.write("\tskeleton \"SingleBone\"\n")
1135
## animFile.write("\t\t\n")
1137
## Config.File.write("\t\".\\anims\\%s_offset.anim\"\n" % animFileName))
1139
## for KeyframeTime in keyframeTimes:
1140
## #Scene.frame_set(KeyframeTime)
1141
## animFile.write("\tCIwAnimKeyFrame\n")
1142
## animFile.write("\t{\n")
1143
## animFile.write("\t\ttime %.2f // frame num %d \n" % (KeyframeTime/Config.AnimFPS, KeyframeTime))
1144
## animFile.write("\t\t\n")
1145
## animFile.write("\t\tbone \"SingleBone\" \n")
1148
## for FCurve in Action.fcurves:
1149
## if FCurve.data_path == "location" and FCurve.array_index == 0: posx = FCurve.evaluate(KeyframeTime)
1151
## for FCurve in Action.fcurves:
1152
## if FCurve.data_path == "location" and FCurve.array_index == 1: posy = FCurve.evaluate(KeyframeTime)
1154
## for FCurve in Action.fcurves:
1155
## if FCurve.data_path == "location" and FCurve.array_index == 2: posz = FCurve.evaluate(KeyframeTime)
1156
## animFile.write("\t\tpos {%.9f,%.9f,%.9f}\n" % (posx, posy, posz))
1160
## for FCurve in Action.fcurves:
1161
## if FCurve.data_path == "rotation_euler" and FCurve.array_index == 1: rot[0] = FCurve.evaluate(KeyframeTime)
1163
## for FCurve in Action.fcurves:
1164
## if FCurve.data_path == "rotation_euler" and FCurve.array_index == 2: rot[1] = FCurve.evaluate(KeyframeTime)
1166
## for FCurve in Action.fcurves:
1167
## if FCurve.data_path == "rotation_euler" and FCurve.array_index == 3: rot[2] = FCurve.evaluate(KeyframeTime)
1168
## rot = rot.to_quaternion()
1169
## animFile.write("\t\trot {%.9f,%.9f,%.9f,%.9f}\n" % (rot[0], rot[1], rot[2], rot[3]))
1172
## for FCurve in Action.fcurves:
1173
## if FCurve.data_path == "scale" and FCurve.array_index == 0: scalex = FCurve.evaluate(KeyframeTime)
1175
## for FCurve in Action.fcurves:
1176
## if FCurve.data_path == "scale" and FCurve.array_index == 1: scaley = FCurve.evaluate(KeyframeTime)
1178
## for FCurve in Action.fcurves:
1179
## if FCurve.data_path == "scale" and FCurve.array_index == 2: scalez = FCurve.evaluate(KeyframeTime)
1180
## animFile.write("\t\t//scale {%.9f,%.9f,%.9f}\n" % (scalex, scaley, scalez))
1182
## animFile.write("\t}\n")
1183
## animFile.write("}\n")
1188
print(" Object %s has no useable animation data." % (StripName(Object.name)))
1190
if Config.ExportArmatures and Object.type == "ARMATURE":
1192
print(" Writing Armature Bone Animation Data...\n")
1193
PoseBones = Object.pose.bones
1194
Bones = Object.data.bones
1195
#riged bones animated
1196
#build key frame time list
1197
keyframeTimes = set()
1198
if Config.ExportAnimationFrames == 1:
1199
# Exports only key frames
1200
for FCurve in Action.fcurves:
1201
for Keyframe in FCurve.keyframe_points:
1202
if Keyframe.co[0] < Scene.frame_start:
1203
keyframeTimes.add(Scene.frame_start)
1204
elif Keyframe.co[0] > Scene.frame_end:
1205
keyframeTimes.add(Scene.frame_end)
1207
keyframeTimes.add(int(Keyframe.co[0]))
1210
keyframeTimes.update(range(Scene.frame_start, Scene.frame_end + 1, 1))
1212
keyframeTimes = list(keyframeTimes)
1213
keyframeTimes.sort()
1215
print("Exporting frames: ")
1216
print(keyframeTimes)
1217
if (Scene.frame_preview_end > Scene.frame_end):
1218
print(" WARNING: END Frame of animation in UI preview is Higher than the Scene Frame end:\n Scene.frame_end %d versus Scene.frame_preview_end %d.\n"
1219
% (Scene.frame_end, Scene.frame_preview_end))
1220
print(" => You might need to change the Scene End Frame, to match the current UI preview frame end...\n=> if you don't want to miss end of animation.\n")
1222
if len(keyframeTimes):
1223
#Create the anim file
1224
animfullname = os.path.dirname(Config.FilePath) + "\\anims\\%s.anim" % animFileName
1225
ensure_dir(animfullname)
1227
print(" Creating anim file (bones animation) %s\n" % (animfullname))
1228
print(" Frame count %d \n" % (len(keyframeTimes)))
1229
animFile = open(animfullname, "w")
1230
animFile.write('// anim file exported from : %r\n' % os.path.basename(bpy.data.filepath))
1231
animFile.write("CIwAnim\n")
1232
animFile.write("{\n")
1233
animFile.write("\tskeleton \"%s\"\n" % (StripName(Object.name)))
1234
animFile.write("\t\t\n")
1236
Config.File.write("\t\".\\anims\\%s.anim\"\n" % animFileName)
1238
for KeyframeTime in keyframeTimes:
1240
print(" Writing Frame %d:" % KeyframeTime)
1241
animFile.write("\tCIwAnimKeyFrame\n")
1242
animFile.write("\t{\n")
1243
animFile.write("\t\ttime %.2f // frame num %d \n" % (KeyframeTime / Config.AnimFPS, KeyframeTime))
1244
#for every frame write bones positions
1245
Scene.frame_set(KeyframeTime)
1246
for PoseBone in PoseBones:
1248
print(" Writing Bone: {}...".format(PoseBone.name))
1249
animFile.write("\t\t\n")
1251
Bone = Bones[PoseBone.name]
1252
WriteBonePosition(Config, Object, Bone, PoseBones, PoseBone, animFile, False)
1254
animFile.write("\t}\n")
1255
animFile.write("}\n")
1259
print(" Object %s has no useable animation data." % (StripName(Object.name)))
1260
if Config.ExportAnimationActions == 1:
1261
#set back the original default animation
1262
Object.animation_data.action = DefaultAction
1264
print(" Done") #Done with Object
1269
################## Utilities
1271
def StripBoneName(name):
1272
return name.replace(" ", "")
1275
def StripName(Name):
1277
def ReplaceSet(String, OldSet, NewChar):
1278
for OldChar in OldSet:
1279
String = String.replace(OldChar, NewChar)
1284
NewName = ReplaceSet(Name, string.punctuation + " ", "_")
1289
d = os.path.dirname(f)
1290
if not os.path.exists(d):
1294
def CloseFile(Config):
1296
print("Closing File...")
1302
CoordinateSystems = (
1303
("1", "Left-Handed", ""),
1304
("2", "Right-Handed", ""),
1308
AnimationFrameModes = (
1310
("1", "Keyframes Only", ""),
1311
("2", "Full Animation", ""),
1314
AnimationActions = (
1315
("0", "Default Animation", ""),
1316
("1", "All Animations", ""),
1320
("1", "All Objects", ""),
1321
("2", "Selected Objects", ""),
1326
("1", "Merge in one big Mesh", ""),
1327
("2", "Merge in unique Geo File containing several meshes", ""),
1331
from bpy.props import StringProperty, EnumProperty, BoolProperty, IntProperty
1334
class MarmaladeExporter(bpy.types.Operator):
1335
"""Export to the Marmalade model format (.group)"""
1337
bl_idname = "export.marmalade"
1338
bl_label = "Export Marmalade"
1340
filepath = StringProperty(subtype='FILE_PATH')
1342
ExportMode = EnumProperty(
1344
description="Select which objects to export. Only Mesh, Empty, " \
1345
"and Armature objects will be exported",
1349
MergeModes = EnumProperty(
1351
description="Select if objects should be merged in one Geo File (it can be usefull if a scene is done by several cube/forms)." \
1352
"Do not merge rigged character that have an armature.",
1357
Scale = IntProperty(
1358
name="Scale Percent",
1359
description="Scale percentage applied for export",
1360
default=100, min=1, max=1000)
1362
FlipNormals = BoolProperty(
1363
name="Flip Normals",
1366
ApplyModifiers = BoolProperty(
1367
name="Apply Modifiers",
1368
description="Apply object modifiers before export",
1370
ExportVertexColors = BoolProperty(
1371
name="Export Vertices Colors",
1372
description="Export colors set on vertices, if any",
1374
ExportMaterialColors = BoolProperty(
1375
name="Export Material Colors",
1376
description="Ambient color is exported on the Material",
1378
ExportTextures = BoolProperty(
1379
name="Export Textures and UVs",
1380
description="Exports UVs and Reference external image files to be used by the model",
1382
CopyTextureFiles = BoolProperty(
1383
name="Copy Textures Files",
1384
description="Copy referenced Textures files in the models\\textures directory",
1386
ExportArmatures = BoolProperty(
1387
name="Export Armatures",
1388
description="Export the bones of any armatures to deform meshes",
1390
ExportAnimationFrames = EnumProperty(
1391
name="Animations Frames",
1392
description="Select the type of animations to export. Only object " \
1393
"and armature bone animations can be exported. Keyframes exports only the keyed frames" \
1394
"Full Animation exports every frames, None disables animationq export. ",
1395
items=AnimationFrameModes,
1397
ExportAnimationActions = EnumProperty(
1398
name="Animations Actions",
1399
description="By default only the Default Animation Action assoiated to an armature is exported." \
1400
"However if you have defined several animations on the same armature,"\
1401
"you can select to export all animations. You can see the list of animation actions in the DopeSheet window.",
1402
items=AnimationActions,
1404
if bpy.context.scene:
1405
defFPS = bpy.context.scene.render.fps
1408
AnimFPS = IntProperty(
1409
name="Animation FPS",
1410
description="Frame rate used to export animation in seconds (can be used to artficially slow down the exported animation, or to speed up it",
1411
default=defFPS, min=1, max=300)
1414
CoordinateSystem = EnumProperty(
1416
description="Select a coordinate system to export to",
1417
items=CoordinateSystems,
1420
Verbose = BoolProperty(
1422
description="Run the exporter in debug mode. Check the console for output",
1425
def execute(self, context):
1427
FilePath = bpy.path.ensure_ext(self.filepath, ".group")
1429
Config = MarmaladeExporterSettings(context,
1431
CoordinateSystem=self.CoordinateSystem,
1432
FlipNormals=self.FlipNormals,
1433
ApplyModifiers=self.ApplyModifiers,
1435
AnimFPS=self.AnimFPS,
1436
ExportVertexColors=self.ExportVertexColors,
1437
ExportMaterialColors=self.ExportMaterialColors,
1438
ExportTextures=self.ExportTextures,
1439
CopyTextureFiles=self.CopyTextureFiles,
1440
ExportArmatures=self.ExportArmatures,
1441
ExportAnimationFrames=self.ExportAnimationFrames,
1442
ExportAnimationActions=self.ExportAnimationActions,
1443
ExportMode=self.ExportMode,
1444
MergeModes=self.MergeModes,
1445
Verbose=self.Verbose)
1447
# Exit edit mode before exporting, so current object states are exported properly.
1448
if bpy.ops.object.mode_set.poll():
1449
bpy.ops.object.mode_set(mode='OBJECT')
1451
ExportMadeWithMarmaladeGroup(Config)
1454
def invoke(self, context, event):
1455
if not self.filepath:
1456
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".group")
1457
WindowManager = context.window_manager
1458
WindowManager.fileselect_add(self)
1459
return {"RUNNING_MODAL"}
1462
def menu_func(self, context):
1463
self.layout.operator(MarmaladeExporter.bl_idname, text="Marmalade cross-platform Apps (.group)")
1467
bpy.utils.register_module(__name__)
1469
bpy.types.INFO_MT_file_export.append(menu_func)
1473
bpy.utils.unregister_module(__name__)
1475
bpy.types.INFO_MT_file_export.remove(menu_func)
1478
if __name__ == "__main__":