Newer
Older
# ***** GPL LICENSE BLOCK *****
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# All rights reserved.
# ***** GPL LICENSE BLOCK *****
# Marmalade SDK is not responsible in any case of the following code.
# This Blender add-on is freely shared for the Blender and Marmalade user communities.
bl_info = {
"name": "Marmalade Cross-platform Apps (.group)",
"author": "Benoit Muller",
"version": (0, 6, 2),
"location": "File > Export > Marmalade cross-platform Apps (.group)",
"description": "Export Marmalade Format files (.group)",
"warning": "",
CoDEmanX
committed
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
"Scripts/Import-Export/Marmalade_Exporter",
"tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
"category": "Import-Export"}
import os
import shutil
from math import radians
import bpy
from mathutils import Matrix
import mathutils
import math
import datetime
import subprocess
#Container for the exporter settings
class MarmaladeExporterSettings:
def __init__(self,
context,
FilePath,
CoordinateSystem=1,
FlipNormals=False,
ApplyModifiers=False,
Scale=100,
AnimFPS=30,
ExportVertexColors=True,
ExportMaterialColors=True,
ExportTextures=True,
CopyTextureFiles=True,
ExportArmatures=False,
ExportAnimationFrames=0,
ExportAnimationActions=0,
ExportMode=1,
MergeModes=0,
Verbose=False):
self.context = context
self.FilePath = FilePath
self.CoordinateSystem = int(CoordinateSystem)
self.FlipNormals = FlipNormals
self.ApplyModifiers = ApplyModifiers
self.Scale = Scale
self.AnimFPS = AnimFPS
self.ExportVertexColors = ExportVertexColors
self.ExportMaterialColors = ExportMaterialColors
self.ExportTextures = ExportTextures
self.CopyTextureFiles = CopyTextureFiles
self.ExportArmatures = ExportArmatures
self.ExportAnimationFrames = int(ExportAnimationFrames)
self.ExportAnimationActions = int(ExportAnimationActions)
self.ExportMode = int(ExportMode)
self.MergeModes = int(MergeModes)
self.Verbose = Verbose
self.WarningList = []
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def ExportMadeWithMarmaladeGroup(Config):
print("----------\nExporting to {}".format(Config.FilePath))
if Config.Verbose:
print("Opening File...")
Config.File = open(Config.FilePath, "w")
if Config.Verbose:
print("Done")
if Config.Verbose:
print("writing group header")
Config.File.write('// Marmalade group file exported from : %s\n' % bpy.data.filepath)
Config.File.write('// Exported %s\n' % str(datetime.datetime.now()))
Config.File.write("CIwResGroup\n{\n\tname \"%s\"\n" % bpy.path.display_name_from_filepath(Config.FilePath))
if Config.Verbose:
print("Generating Object list for export... (Root parents only)")
if Config.ExportMode == 1:
Config.ExportList = [Object for Object in Config.context.scene.objects
if Object.type in {'ARMATURE', 'EMPTY', 'MESH'}
and Object.parent is None]
else:
ExportList = [Object for Object in Config.context.selected_objects
if Object.type in {'ARMATURE', 'EMPTY', 'MESH'}]
Config.ExportList = [Object for Object in ExportList
if Object.parent not in ExportList]
if Config.Verbose:
print(" List: {}\nDone".format(Config.ExportList))
if Config.Verbose:
print("Setting up...")
if Config.ExportAnimationFrames:
if Config.Verbose:
print(bpy.context.scene)
print(bpy.context.scene.frame_current)
CurrentFrame = bpy.context.scene.frame_current
if Config.Verbose:
print("Done")
CoDEmanX
committed
Config.ObjectList = []
if Config.Verbose:
print("Writing Objects...")
WriteObjects(Config, Config.ExportList)
if Config.Verbose:
print("Done")
if Config.Verbose:
print("Objects Exported: {}".format(Config.ExportList))
if Config.ExportAnimationFrames:
if Config.Verbose:
print("Writing Animation...")
WriteKeyedAnimationSet(Config, bpy.context.scene)
bpy.context.scene.frame_current = CurrentFrame
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
if Config.Verbose:
print("Done")
Config.File.write("}\n")
CloseFile(Config)
print("Finished")
def GetObjectChildren(Parent):
return [Object for Object in Parent.children
if Object.type in {'ARMATURE', 'EMPTY', 'MESH'}]
#Returns the file path of first image texture from Material.
def GetMaterialTextureFullPath(Config, Material):
if Material:
#Create a list of Textures that have type "IMAGE"
ImageTextures = [Material.texture_slots[TextureSlot].texture for TextureSlot in Material.texture_slots.keys() if Material.texture_slots[TextureSlot].texture.type == "IMAGE"]
#Refine a new list with only image textures that have a file source
TexImages = [Texture.image for Texture in ImageTextures if getattr(Texture.image, "source", "") == "FILE"]
ImageFiles = [Texture.image.filepath for Texture in ImageTextures if getattr(Texture.image, "source", "") == "FILE"]
if TexImages:
filepath = TexImages[0].filepath
if TexImages[0].packed_file:
TexImages[0].unpack()
if not os.path.exists(filepath):
#try relative path to the blend file
filepath = os.path.dirname(bpy.data.filepath) + filepath
#Marmalade doesn't like jpeg/tif so try to convert in png on the fly
if (TexImages[0].file_format == 'JPEG' or TexImages[0].file_format == 'TIFF') and os.path.exists(filepath):
marmaladeConvert = os.path.expandvars("%S3E_DIR%\\..\\tools\\ImageMagick\\win32\\convert.exe")
if (os.path.exists(marmaladeConvert)):
srcImagefilepath = filepath
filepath = os.path.splitext(filepath)[0] + '.png'
if Config.Verbose:
print(" /!\\ Converting Texture %s in PNG: %s{}..." % (TexImages[0].file_format, filepath))
print('"%s" "%s" "%s"' % (marmaladeConvert, srcImagefilepath, filepath))
subprocess.call([marmaladeConvert, srcImagefilepath, filepath])
return filepath
return None
def WriteObjects(Config, ObjectList, geoFile=None, mtlFile=None, GeoModel=None, bChildObjects=False):
Config.ObjectList += ObjectList
if bChildObjects == False and Config.MergeModes > 0:
if geoFile == None:
#we merge objects, so use name of group file for the name of Geo
geoFile, mtlFile = CreateGeoMtlFiles(Config, bpy.path.display_name_from_filepath(Config.FilePath))
GeoModel = CGeoModel(bpy.path.display_name_from_filepath(Config.FilePath))
for Object in ObjectList:
if Config.Verbose:
print(" Writing Object: {}...".format(Object.name))
CoDEmanX
committed
if Config.ExportArmatures and Object.type == "ARMATURE":
Armature = Object.data
ParentList = [Bone for Bone in Armature.bones if Bone.parent is None]
if Config.Verbose:
print(" Writing Armature Bones...")
#Create the skel file
skelfullname = os.path.dirname(Config.FilePath) + os.sep + "models" + os.sep + "%s.skel" % (StripName(Object.name))
ensure_dir(skelfullname)
if Config.Verbose:
print(" Creating skel file %s" % (skelfullname))
skelFile = open(skelfullname, "w")
CoDEmanX
committed
skelFile.write('// skel file exported from : %r\n' % os.path.basename(bpy.data.filepath))
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
skelFile.write("CIwAnimSkel\n")
skelFile.write("{\n")
skelFile.write("\tnumBones %d\n" % (len(Armature.bones)))
Config.File.write("\t\".\models\%s.skel\"\n" % (StripName(Object.name)))
WriteArmatureParentRootBones(Config, Object, ParentList, skelFile)
skelFile.write("}\n")
skelFile.close()
if Config.Verbose:
print(" Done")
ChildList = GetObjectChildren(Object)
if Config.ExportMode == 2: # Selected Objects Only
ChildList = [Child for Child in ChildList
if Child in Config.context.selected_objects]
if Config.Verbose:
print(" Writing Children...")
WriteObjects(Config, ChildList, geoFile, mtlFile, GeoModel, True)
if Config.Verbose:
print(" Done Writing Children")
if Object.type == "MESH":
if Config.Verbose:
print(" Generating Mesh...")
if Config.ApplyModifiers:
if Config.ExportArmatures:
#Create a copy of the object and remove all armature modifiers so an unshaped
#mesh can be created from it.
Object2 = Object.copy()
for Modifier in [Modifier for Modifier in Object2.modifiers if Modifier.type == "ARMATURE"]:
Object2.modifiers.remove(Modifier)
Mesh = Object2.to_mesh(bpy.context.scene, True, "PREVIEW")
else:
Mesh = Object.to_mesh(bpy.context.scene, True, "PREVIEW")
else:
Mesh = Object.to_mesh(bpy.context.scene, False, "PREVIEW")
if Config.Verbose:
print(" Done")
print(" Writing Mesh...")
# Flip ZY axis (Blender Z up: Marmalade: Y up) ans Scale appropriately
X_ROT = mathutils.Matrix.Rotation(-math.pi / 2, 4, 'X')
if Config.MergeModes == 0:
# No merge, so all objects are exported in MODEL SPACE and not in world space
# Calculate Scale of the Export
meshScale = Object.matrix_world.to_scale() # Export is working, even if user doesn't have use apply scale in Edit mode.
scalematrix = Matrix()
scalematrix[0][0] = meshScale.x * Config.Scale
scalematrix[1][1] = meshScale.y * Config.Scale
scalematrix[2][2] = meshScale.z * Config.Scale
meshRot = Object.matrix_world.to_quaternion() # Export is working, even if user doesn't have use apply Rotation in Edit mode.
Mesh.transform(X_ROT * meshRot.to_matrix().to_4x4() * scalematrix)
else:
# In Merge mode, we need to keep relative postion of each objects, so we export in WORLD SPACE
SCALE_MAT = mathutils.Matrix.Scale(Config.Scale, 4)
Benoit Muller
committed
Mesh.transform(SCALE_MAT * X_ROT * Object.matrix_world)
# manage merge options
CoDEmanX
committed
if Config.MergeModes == 0:
#one geo per Object, so use name of Object for the Geo file
geoFile, mtlFile = CreateGeoMtlFiles(Config, StripName(Object.name))
CoDEmanX
committed
GeoModel = CGeoModel(StripName(Object.name))
# Write the Mesh in the Geo file
WriteMesh(Config, Object, Mesh, geoFile, mtlFile, GeoModel)
if Config.MergeModes == 0:
# no merge so finalize the file, and discard the file and geo class
FinalizeGeoMtlFiles(Config, geoFile, mtlFile)
geoFile = None
mtlFile = None
GeoModel = None
elif Config.MergeModes == 1:
# merge in one Mesh, so keep the Geo class and prepare to change object
CoDEmanX
committed
GeoModel.NewObject()
elif Config.MergeModes == 2:
# merge several Meshes in one file: so clear the mesh data that we just written in the file,
# but keep Materials info that need to be merged across objects
GeoModel.ClearAllExceptMaterials()
if Config.Verbose:
print(" Done")
if Config.ApplyModifiers and Config.ExportArmatures:
bpy.data.objects.remove(Object2)
bpy.data.meshes.remove(Mesh)
if Config.Verbose:
print(" Done Writing Object: {}".format(Object.name))
if bChildObjects == False:
# we have finish to do all objects
if GeoModel:
if Config.MergeModes == 1:
# we have Merges all objects in one Mesh, so time to write this big mesh in the file
GeoModel.PrintGeoMesh(geoFile)
# time to write skinfile if any
if len(GeoModel.useBonesDict) > 0:
Benoit Muller
committed
# some mesh was not modified by the armature. so we must skinned the merged mesh.
# So unskinned vertices from unarmatured meshes, are assigned to the root bone of the armature
for i in range(0, len(GeoModel.vList)):
if not i in GeoModel.skinnedVertices:
GeoModel.skinnedVertices.append(i)
useBonesKey = pow(2, GeoModel.armatureRootBoneIndex)
vertexGroupIndices = list((GeoModel.armatureRootBoneIndex,))
CoDEmanX
committed
if useBonesKey not in GeoModel.useBonesDict:
Benoit Muller
committed
GeoModel.mapVertexGroupNames[GeoModel.armatureRootBoneIndex] = StripBoneName(GeoModel.armatureRootBone.name)
VertexList = []
VertexList.append("\t\tvertWeights { %d, 1.0}" % i)
GeoModel.useBonesDict[useBonesKey] = (vertexGroupIndices, VertexList)
else:
pair_ListGroupIndices_ListAssignedVertices = GeoModel.useBonesDict[useBonesKey]
pair_ListGroupIndices_ListAssignedVertices[1].append("\t\tvertWeights { %d, 1.0}" % i)
GeoModel.useBonesDict[useBonesKey] = pair_ListGroupIndices_ListAssignedVertices
# now generates the skin file
PrintSkinWeights(Config, GeoModel.armatureObjectName, GeoModel.useBonesDict, GeoModel.mapVertexGroupNames, GeoModel.name)
if Config.MergeModes > 0:
WriteMeshMaterialsForGeoModel(Config, mtlFile, GeoModel)
FinalizeGeoMtlFiles(Config, geoFile, mtlFile)
geoFile = None
mtlFile = None
GeoModel = None
def CreateGeoMtlFiles(Config, Name):
#Create the geo file
geofullname = os.path.dirname(Config.FilePath) + os.sep + "models" + os.sep + "%s.geo" % Name
ensure_dir(geofullname)
if Config.Verbose:
CoDEmanX
committed
print(" Creating geo file %s" % (geofullname))
geoFile = open(geofullname, "w")
geoFile.write('// geo file exported from : %r\n' % os.path.basename(bpy.data.filepath))
geoFile.write("CIwModel\n")
geoFile.write("{\n")
geoFile.write("\tname \"%s\"\n" % Name)
# add it to the group
Config.File.write("\t\".\models\%s.geo\"\n" % Name)
# Create the mtl file
mtlfullname = os.path.dirname(Config.FilePath) + os.sep + "models" + os.sep + "%s.mtl" % Name
ensure_dir(mtlfullname)
if Config.Verbose:
print(" Creating mtl file %s" % (mtlfullname))
mtlFile = open(mtlfullname, "w")
CoDEmanX
committed
mtlFile.write('// mtl file exported from : %r\n' % os.path.basename(bpy.data.filepath))
return geoFile, mtlFile
def FinalizeGeoMtlFiles(Config, geoFile, mtlFile):
if Config.Verbose:
CoDEmanX
committed
print(" Closing geo file")
geoFile.write("}\n")
geoFile.close()
if Config.Verbose:
CoDEmanX
committed
print(" Closing mtl file")
mtlFile.close()
def WriteMesh(Config, Object, Mesh, geoFile=None, mtlFile=None, GeoModel=None):
if geoFile == None or mtlFile == None:
print (" ERROR not geo file arguments in WriteMesh method")
return
if GeoModel == None:
print (" ERROR not GeoModel arguments in WriteMesh method")
return
BuildOptimizedGeo(Config, Object, Mesh, GeoModel)
if Config.MergeModes == 0 or Config.MergeModes == 2:
#if we don't merge, or if we write several meshes into one file ... write the mesh everytime we do an object
GeoModel.PrintGeoMesh(geoFile)
CoDEmanX
committed
if Config.Verbose:
print(" Done\n Writing Mesh Materials...")
if Config.MergeModes == 0:
#No merge, so we can diretly write the Mtl file associated to this object
WriteMeshMaterialsForGeoModel(Config, mtlFile, GeoModel)
if Config.Verbose:
print(" Done")
CoDEmanX
committed
if Config.ExportArmatures:
if Config.Verbose:
print(" Writing Mesh Weights...")
WriteMeshSkinWeightsForGeoModel(Config, Object, Mesh, GeoModel)
if Config.Verbose:
print(" Done")
###### optimized version fo Export, can be used also to merge several object in one single geo File ######
# CGeoModel
# -> List Vertices
# -> List Normales
# -> List uv 0
# -> List uv 1
# -> List Vertex Colors
# -> List Materials
# -> Material name
# -> Blender Material Object
# -> List Tris -> Stream Indices v,vn,uv0,uv1,vc
# -> List Quads -> Stream Indices v,vn,uv0,uv1,vc
#############
#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
CoDEmanX
committed
#############
__slots__ = "v", "vn", "uv0", "uv1", "vc"
CoDEmanX
committed
def __init__(self, v, vn, uv0, uv1, vc):
self.v = v
self.vn = vn
self.uv0 = uv0
self.uv1 = uv1
self.vc = vc
CoDEmanX
committed
#############
#Store a Quad or a Tri in marmalade geo format : 3 or 4 CIndexList depending it is a Tri or a Quad
CoDEmanX
committed
#############
__slots__ = "pointsList",
CoDEmanX
committed
self.pointsList = []
def AddPoint(self, v, vn, uv0, uv1, vc):
self.pointsList.append( CGeoIndexList(v, vn, uv0, uv1, vc))
def PointsCount(self):
return len(self.pointsList)
def PrintPoly(self, geoFile):
if len(self.pointsList) == 3:
geoFile.write("\t\t\t\tt ")
if len(self.pointsList) == 4:
geoFile.write("\t\t\t\tq ")
for point in self.pointsList:
geoFile.write(" {%d, %d, %d, %d, %d}" % (point.v, point.vn, point.uv0, point.uv1, point.vc))
geoFile.write("\n")
#############
#Store all the poly (tri or quad) assigned to a Material in marmalade geo format
CoDEmanX
committed
#############
__slots__ = "name", "material", "quadList", "triList", "currentPoly"
CoDEmanX
committed
def __init__(self, name, material=None):
self.name = name
self.material = material
self.quadList = []
self.triList = []
self.currentPoly = None
def BeginPoly(self):
self.currentPoly = CGeoPoly()
def AddPoint(self, v, vn, uv0, uv1, vc):
CoDEmanX
committed
self.currentPoly.AddPoint(v, vn, uv0, uv1, vc)
def EndPoly(self):
if (self.currentPoly.PointsCount() == 3):
self.triList.append(self.currentPoly)
if (self.currentPoly.PointsCount() == 4):
self.quadList.append(self.currentPoly)
self.currentPoly = None
def ClearPolys(self):
self.quadList = []
self.triList = []
self.currentPoly = None
def PrintMaterialPolys(self, geoFile):
geoFile.write("\t\tCSurface\n")
geoFile.write("\t\t{\n")
geoFile.write("\t\t\tmaterial \"%s\"\n" % self.name)
geoFile.write("\t\t\tCTris\n")
geoFile.write("\t\t\t{\n")
geoFile.write("\t\t\t\tnumTris %d\n" % (len(self.triList)))
for poly in self.triList:
poly.PrintPoly(geoFile)
geoFile.write("\t\t\t}\n")
geoFile.write("\t\t\tCQuads\n")
geoFile.write("\t\t\t{\n")
geoFile.write("\t\t\t\tnumQuads %d\n" % (len(self.quadList)))
for poly in self.quadList:
poly.PrintPoly(geoFile)
geoFile.write("\t\t\t}\n")
geoFile.write("\t\t}\n")
#############
#Store all the information on a Model/Mesh (vertices, normal, certcies color, uv0, uv1, TRI, QUAD) in marmalade geo format
CoDEmanX
committed
#############
__slots__ = ("name", "MaterialsDict", "vList", "vnList", "vcList", "uv0List", "uv1List",
"currentMaterialPolys", "vbaseIndex","vnbaseIndex", "uv0baseIndex", "uv1baseIndex",
Benoit Muller
committed
"armatureObjectName", "useBonesDict", "mapVertexGroupNames", "armatureRootBone", "armatureRootBoneIndex", "skinnedVertices")
CoDEmanX
committed
def __init__(self, name):
self.name = name
self.MaterialsDict = {}
self.vList = []
self.vnList = []
self.vcList = []
self.uv0List = []
self.uv1List = []
self.currentMaterialPolys = None
#used xx baseIndex are used when merging several blender objects into one Mesh in the geo file (internal offset)
self.vbaseIndex = 0
self.vnbaseIndex = 0
self.uv0baseIndex = 0
self.uv1baseIndex = 0
# Store some information for skin management , when we merge several object in one big mesh (MergeModes 1)
# can only work if in the object list only one is rigged with an armature... and if it is located in 0,0,0
self.armatureObjectName = ""
#useBonesKey : bit field, where each bit is a VertexGroup.Index): Sum(2^VertGroupIndex).
#useBonesDict[useBonesKey] = tuple(VertexGroups.group, list(Vertex))
self.useBonesDict = {}
self.mapVertexGroupNames = {}
Benoit Muller
committed
self.armatureRootBone = None
self.armatureRootBoneIndex = 0
self.skinnedVertices = []
def AddVertex(self, vertex):
self.vList.append(vertex.copy())
def AddVertexNormal(self, vertexN):
self.vnList.append(vertexN.copy())
# 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)
def AddVertexUV0(self, u, v):
self.uv0List.append((u, v))
CoDEmanX
committed
return len(self.uv0List) - 1 - self.uv0baseIndex
def AddVertexUV1(self, u, v):
self.uv1List.append((u, v))
CoDEmanX
committed
return len(self.uv1List) - 1 - self.uv1baseIndex
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
# 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)
def AddVertexColor(self, r, g, b, a):
for i in range(0, len(self.vcList)):
col = self.vcList[i]
if col[0] == r and col[1] == g and col[2] == b and col[3] == a:
return i
self.vcList.append((r, g, b, a))
return len(self.vcList)-1
def BeginPoly(self, MaterialName, material=None):
if MaterialName not in self.MaterialsDict:
self.currentMaterialPolys = CGeoMaterialPolys(MaterialName, material)
else:
self.currentMaterialPolys = self.MaterialsDict[MaterialName]
self.currentMaterialPolys.BeginPoly()
def AddPoint(self, v, vn, uv0, uv1, vc):
if v != -1:
v += self.vbaseIndex
if vn != -1:
vn += self.vnbaseIndex
if uv0 != -1:
uv0 += self.uv0baseIndex
if uv1 != -1:
uv1 += self.uv1baseIndex
CoDEmanX
committed
self.currentMaterialPolys.AddPoint(v, vn, uv0, uv1, vc)
def EndPoly(self):
self.currentMaterialPolys.EndPoly()
self.MaterialsDict[self.currentMaterialPolys.name] = self.currentMaterialPolys
self.currentMaterialPolys = None
def NewObject(self):
#used in Merge mode 1: allows to merge several blender objects into one Mesh.
self.vbaseIndex = len(self.vList)
self.vnbaseIndex = len(self.vnList)
self.uv0baseIndex = len(self.uv0List)
self.uv1baseIndex = len(self.uv1List)
def ClearAllExceptMaterials(self):
#used in Merge mode 2: one geo with several mesh
self.vList = []
self.vnList = []
self.vcList = []
self.uv0List = []
self.uv1List = []
self.currentMaterialPolys = None
self.vbaseIndex = 0
self.vnbaseIndex = 0
self.uv0baseIndex = 0
self.uv1baseIndex = 0
for GeoMaterialPolys in self.MaterialsDict.values():
GeoMaterialPolys.ClearPolys()
self.useBonesDict = {}
self.mapVertexGroupNames = {}
self.armatureObjectName = ""
Benoit Muller
committed
self.armatureRootBone = None
self.armatureRootBoneIndex = 0
self.skinnedVertices = []
def PrintGeoMesh(self, geoFile):
geoFile.write("\tCMesh\n")
geoFile.write("\t{\n")
geoFile.write("\t\tname \"%s\"\n" % (StripName(self.name)))
geoFile.write("\t\tCVerts\n")
geoFile.write("\t\t{\n")
geoFile.write("\t\t\tnumVerts %d\n" % len(self.vList))
for vertex in self.vList:
CoDEmanX
committed
geoFile.write("\t\t\tv { %.9f, %.9f, %.9f }\n" % (vertex[0], vertex[1], vertex[2]))
geoFile.write("\t\t}\n")
geoFile.write("\t\tCVertNorms\n")
geoFile.write("\t\t{\n")
geoFile.write("\t\t\tnumVertNorms %d\n" % len(self.vnList))
for vertexn in self.vnList:
CoDEmanX
committed
geoFile.write("\t\t\tvn { %.9f, %.9f, %.9f }\n" % (vertexn[0], vertexn[1], vertexn[2]))
geoFile.write("\t\t}\n")
geoFile.write("\t\tCVertCols\n")
geoFile.write("\t\t{\n")
geoFile.write("\t\t\tnumVertCols %d\n" % len(self.vcList))
for color in self.vcList:
CoDEmanX
committed
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
geoFile.write("\t\t}\n")
geoFile.write("\t\tCUVs\n")
geoFile.write("\t\t{\n")
geoFile.write("\t\t\tsetID 0\n")
geoFile.write("\t\t\tnumUVs %d\n" % len(self.uv0List))
for uv in self.uv0List:
CoDEmanX
committed
geoFile.write("\t\t\tuv { %.9f, %.9f }\n" % (uv[0], uv[1]))
geoFile.write("\t\t}\n")
geoFile.write("\t\tCUVs\n")
geoFile.write("\t\t{\n")
geoFile.write("\t\t\tsetID 1\n")
geoFile.write("\t\t\tnumUVs %d\n" % len(self.uv1List))
for uv in self.uv1List:
CoDEmanX
committed
geoFile.write("\t\t\tuv { %.9f, %.9f }\n" % (uv[0], uv[1]))
geoFile.write("\t\t}\n")
for GeoMaterialPolys in self.MaterialsDict.values():
GeoMaterialPolys.PrintMaterialPolys(geoFile)
geoFile.write("\t}\n")
def GetMaterialList(self):
return list(self.MaterialsDict.keys())
def GetMaterialByName(self, name):
if name in self.MaterialsDict:
return self.MaterialsDict[name].material
else:
CoDEmanX
committed
return None
#############
# iterates faces, vertices ... and store the information in the GeoModel container
def BuildOptimizedGeo(Config, Object, Mesh, GeoModel):
if GeoModel == None:
GeoModel = CGeoModel(filename, Object.name)
#Ensure tessfaces data are here
Mesh.update (calc_tessface=True)
CoDEmanX
committed
#Store Vertex stream, and Normal stream (use directly the order from blender collection
for Vertex in Mesh.vertices:
GeoModel.AddVertex(Vertex.co)
Normal = Vertex.normal
if Config.FlipNormals:
Normal = -Normal
GeoModel.AddVertexNormal(Normal)
#Check if some colors have been defined
if Config.ExportVertexColors and (len(Mesh.vertex_colors) > 0):
vertexColors = Mesh.tessface_vertex_colors[0].data
#Check if some uv coordinates have been defined
UVCoordinates = None
if Config.ExportTextures and (len(Mesh.uv_textures) > 0):
for UV in Mesh.tessface_uv_textures:
if UV.active_render:
UVCoordinates = UV.data
break
#Iterate on Faces and Store the poly (quad or tri) and the associate colors,UVs
for Face in Mesh.tessfaces:
# stream for vertex (we use the same for normal)
Vertices = list(Face.vertices)
if Config.CoordinateSystem == 1:
Vertices = Vertices[::-1]
# stream for vertex colors
if vertexColors:
MeshColor = vertexColors[Face.index]
if len(Vertices) == 3:
FaceColors = list((MeshColor.color1, MeshColor.color2, MeshColor.color3))
else:
FaceColors = list((MeshColor.color1, MeshColor.color2, MeshColor.color3, MeshColor.color4))
if Config.CoordinateSystem == 1:
FaceColors = FaceColors[::-1]
for color in FaceColors:
index = GeoModel.AddVertexColor(color[0], color[1], color[2], 1) #rgba => no alpha on vertex color in Blender so use 1
colorIndex.append(index)
else:
colorIndex = list((-1,-1,-1,-1))
# stream for UV0 coordinates
if UVCoordinates:
uvFace = UVCoordinates[Face.index]
uvVertices = []
for uvVertex in uvFace.uv:
uvVertices.append(tuple(uvVertex))
if Config.CoordinateSystem == 1:
uvVertices = uvVertices[::-1]
for uvVertex in uvVertices:
CoDEmanX
committed
index = GeoModel.AddVertexUV0(uvVertex[0], 1 - uvVertex[1])
uv0Index.append(index)
else:
uv0Index = list((-1, -1, -1, -1))
# stream for UV1 coordinates
uv1Index = list((-1, -1, -1, -1))
mat = None
# find the associated material
if Face.material_index < len(Mesh.materials):
mat = Mesh.materials[Face.material_index]
if mat:
matName = mat.name
else:
CoDEmanX
committed
matName = "NoMaterialAssigned" # There is no material assigned in blender !!!, exporter have generated a default one
# now on the material, generates the tri/quad in v,vn,uv0,uv1,vc stream index
GeoModel.BeginPoly(matName, mat)
for i in range(0, len(Vertices)):
GeoModel.AddPoint(Vertices[i], Vertices[i], uv0Index[i], uv1Index[i], colorIndex[i])
GeoModel.EndPoly()
CoDEmanX
committed
#############
# Get the list of Material in use by the CGeoModel
def WriteMeshMaterialsForGeoModel(Config, mtlFile, GeoModel):
for matName in GeoModel.GetMaterialList():
Material = GeoModel.GetMaterialByName(matName)
WriteMaterial(Config, mtlFile, Material)
def WriteMaterial(Config, mtlFile, Material=None):
mtlFile.write("CIwMaterial\n")
mtlFile.write("{\n")
if Material:
mtlFile.write("\tname \"%s\"\n" % Material.name)
if Config.ExportMaterialColors:
#if bpy.context.scene.world:
# MatAmbientColor = Material.ambient * bpy.context.scene.world.ambient_color
MatAmbientColor = Material.ambient * Material.diffuse_color
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)))
MatDiffuseColor = 255 * Material.diffuse_intensity * Material.diffuse_color
MatDiffuseColor = min((255, 255, 255)[:],MatDiffuseColor[:])
mtlFile.write("\tcolDiffuse {%.2f,%.2f,%.2f} \n" % (MatDiffuseColor[:]))
MatSpecularColor = 255 * Material.specular_intensity * Material.specular_color
MatSpecularColor = min((255, 255, 255)[:],MatSpecularColor[:])
mtlFile.write("\tcolSpecular {%.2f,%.2f,%.2f} \n" % (MatSpecularColor[:]))
# EmitColor = Material.emit * Material.diffuse_color
CoDEmanX
committed
# mtlFile.write("\tcolEmissive {%.2f,%.2f,%.2f} \n" % (EmitColor* 255)[:])
else:
mtlFile.write("\tname \"NoMaterialAssigned\" // There is no material assigned in blender !!!, exporter have generated a default one\n")
#Copy texture
if Config.ExportTextures:
Texture = GetMaterialTextureFullPath(Config, Material)
if Texture:
mtlFile.write("\ttexture0 .\\textures\\%s\n" % (bpy.path.basename(Texture)))
CoDEmanX
committed
if Config.CopyTextureFiles:
if not os.path.exists(Texture):
#try relative path to the blend file
Texture = os.path.dirname(bpy.data.filepath) + Texture
if os.path.exists(Texture):
textureDest = os.path.dirname(Config.FilePath) + os.sep + "models" + os.sep + "textures" + os.sep + ("%s" % bpy.path.basename(Texture))
ensure_dir(textureDest)
if Config.Verbose:
print(" Copying the texture file %s ---> %s" % (Texture, textureDest))
shutil.copy(Texture, textureDest)
else:
if Config.Verbose:
print(" CANNOT Copy texture file (not found) %s" % (Texture))
mtlFile.write("}\n")
Benoit Muller
committed
def GetFirstRootBone(ArmatureObject):
ArmatureBones = ArmatureObject.data.bones
ParentBoneList = [Bone for Bone in ArmatureBones if Bone.parent is None]
if ParentBoneList:
return ParentBoneList[0]
return None
def GetVertexGroupFromBone(Object, Bone):
if Bone:
vertexGroupList = [VertexGroup for VertexGroup in Object.vertex_groups if VertexGroup.name == Bone.name]
if vertexGroupList:
return vertexGroupList[0]
Benoit Muller
committed
return None
def GetBoneListNames(Bones):
boneList = []
for Bone in Bones:
boneList.append(Bone.name)
boneList += GetBoneListNames(Bone.children)
return boneList
Benoit Muller
committed
def FindUniqueIndexForRootBone(Object, RootVertexGroup):
if RootVertexGroup:
return RootVertexGroup.index
else:
#If there is not VertexGroup associated to the root bone name, we don't have a vertex index.
#so use the next available free index
return len(Object.vertex_groups)
CoDEmanX
committed
def WriteMeshSkinWeightsForGeoModel(Config, Object, Mesh, GeoModel):
ArmatureList = [Modifier for Modifier in Object.modifiers if Modifier.type == "ARMATURE"]
if ArmatureList:
ArmatureObject = ArmatureList[0].object
if ArmatureObject is None:
return
Benoit Muller
committed
RootBone = GetFirstRootBone(ArmatureObject)
RootVertexGroup = GetVertexGroupFromBone(Object, RootBone)
BoneNames = GetBoneListNames(ArmatureObject.data.bones)
GeoModel.armatureObjectName = StripName(ArmatureObject.name)
Benoit Muller
committed
if RootBone:
GeoModel.armatureRootBone = RootBone
GeoModel.armatureRootBoneIndex = FindUniqueIndexForRootBone(Object, RootVertexGroup)
Benoit Muller
committed
# Marmalade need to declare a vertex per list of affected bones
# so first we have to get all the combinations of affected bones that exist in the mesh
# 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
CoDEmanX
committed
for Vertex in Mesh.vertices:
VertexIndex = Vertex.index + GeoModel.vbaseIndex
AddVertexToDicionarySkinWeights(Config, Object, Mesh, Vertex, GeoModel.useBonesDict, GeoModel.mapVertexGroupNames, VertexIndex, RootBone, RootVertexGroup, BoneNames)
Benoit Muller
committed
GeoModel.skinnedVertices.append(VertexIndex)
if Config.MergeModes != 1:
# write skin file directly
PrintSkinWeights(Config, GeoModel.armatureObjectName, GeoModel.useBonesDict, GeoModel.mapVertexGroupNames, StripName(Object.name))
CoDEmanX
committed
def PrintSkinWeights(Config, ArmatureObjectName, useBonesDict, mapVertexGroupNames, GeoName):
skinfullname = os.path.dirname(Config.FilePath) + os.sep + "models" + os.sep + "%s.skin" % GeoName
ensure_dir(skinfullname)
if Config.Verbose:
print(" Creating skin file %s" % (skinfullname))
skinFile = open(skinfullname, "w")
CoDEmanX
committed
skinFile.write('// skin file exported from : %r\n' % os.path.basename(bpy.data.filepath))
skinFile.write("CIwAnimSkin\n")
skinFile.write("{\n")
skinFile.write("\tskeleton \"%s\"\n" % ArmatureObjectName)
skinFile.write("\tmodel \"%s\"\n" % GeoName)
# now we have Bones grouped in the dictionary , along with the associated influenced vertex weighting
# So simply iterate the dictionary
Config.File.write("\t\".\models\%s.skin\"\n" % GeoName)
for pair_ListGroupIndices_ListAssignedVertices in useBonesDict.values():
skinFile.write("\tCIwAnimSkinSet\n")
skinFile.write("\t{\n")
skinFile.write("\t\tuseBones {")
for vertexGroupIndex in pair_ListGroupIndices_ListAssignedVertices[0]:
skinFile.write(" %s" % mapVertexGroupNames[vertexGroupIndex])
skinFile.write(" }\n")
skinFile.write("\t\tnumVerts %d\n" % len(pair_ListGroupIndices_ListAssignedVertices[1]))
for VertexWeightString in pair_ListGroupIndices_ListAssignedVertices[1]:
skinFile.write(VertexWeightString)
skinFile.write("\t}\n")
skinFile.write("}\n")
skinFile.close()
def AddVertexToDicionarySkinWeights(Config, Object, Mesh, Vertex, useBonesDict, mapVertexGroupNames, VertexIndex, RootBone, RootVertexGroup, BoneNames):
#build useBones
useBonesKey = 0
vertexGroupIndices = []
weightTotal = 0.0
if (len(Vertex.groups)) > 4:
print ("ERROR Vertex %d is influenced by more than 4 bones\n" % (VertexIndex))
for VertexGroup in Vertex.groups:
Benoit Muller
committed
if (VertexGroup.weight > 0):
groupName = Object.vertex_groups[VertexGroup.group].name
if groupName in BoneNames:
mapVertexGroupNames[VertexGroup.group] = StripBoneName(groupName)
if (len(vertexGroupIndices))<4: #ignore if more 4 bones are influencing the vertex
useBonesKey = useBonesKey + pow(2, VertexGroup.group)
vertexGroupIndices.append(VertexGroup.group)
weightTotal = weightTotal + VertexGroup.weight
Benoit Muller
committed
bWeightTotZero = True #avoid divide by zero later on
if (RootBone):
if Config.Verbose:
print(" Warning Weight is ZERO for vertex %d => Add it to the root bone" % (VertexIndex))
RootBoneGroupIndex = FindUniqueIndexForRootBone(Object, RootVertexGroup)
mapVertexGroupNames[RootBoneGroupIndex] = StripBoneName(RootBone.name)
useBonesKey = pow(2, RootBoneGroupIndex)
vertexGroupIndices = list((RootBoneGroupIndex,))
weightTotal = 1
else:
bWeightTotZero = False
CoDEmanX
committed
if len(vertexGroupIndices) > 0:
vertexGroupIndices.sort();
CoDEmanX
committed
#build the vertex weight string: vertex indices, followed by influence weight for each bone
VertexWeightString = "\t\tvertWeights { %d" % (VertexIndex)
for vertexGroupIndex in vertexGroupIndices:
#get the weight of this specific VertexGroup (aka bone)
boneWeight = 1
for VertexGroup in Vertex.groups:
if VertexGroup.group == vertexGroupIndex:
boneWeight = VertexGroup.weight
#calculate the influence of this bone compared to the total of weighting applied to this Vertex
if not bWeightTotZero:
VertexWeightString += ", %.7f" % (boneWeight / weightTotal)
else:
VertexWeightString += ", %.7f" % (1.0 / len(vertexGroupIndices))
VertexWeightString += "}"
if bWeightTotZero:
CoDEmanX
committed
VertexWeightString += " // total weight was zero in blender , export assign it to the RootBone with weight 1."
if (len(Vertex.groups)) > 4:
VertexWeightString += " // vertex is associated to more than 4 bones in blender !! skip some bone association (was associated to %d bones)." % (len(Vertex.groups))
VertexWeightString += "\n"
CoDEmanX
committed
#store in dictionnary information
if useBonesKey not in useBonesDict:
VertexList.append(VertexWeightString)
useBonesDict[useBonesKey] = (vertexGroupIndices, VertexList)
else:
pair_ListGroupIndices_ListAssignedVertices = useBonesDict[useBonesKey]
pair_ListGroupIndices_ListAssignedVertices[1].append(VertexWeightString)
useBonesDict[useBonesKey] = pair_ListGroupIndices_ListAssignedVertices
else:
CoDEmanX
committed
print ("ERROR Vertex %d is not skinned (it doesn't belong to any vertex group\n" % (VertexIndex))
CoDEmanX
committed
############# ARMATURE: Bone export, and Bone animation export
def WriteArmatureParentRootBones(Config, Object, RootBonesList, skelFile):
if len(RootBonesList) > 1:
print(" /!\\ WARNING ,Marmelade need only one ROOT bone per armature, there is %d root bones " % len(RootBonesList))
print(RootBonesList)
CoDEmanX
committed
PoseBones = Object.pose.bones