mesh.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. import sys
  2. import os
  3. from copy import deepcopy
  4. from Blender import Mathutils
  5. from Blender.Mathutils import *
  6. #=======================================================================================================================
  7. # Initializer =
  8. #=======================================================================================================================
  9. class Initializer:
  10. def __init__(self):
  11. self.blMesh = None # Blender Mesh
  12. self.blSkeleton = None # Blender Armature
  13. self.mtlName = "" # Material name
  14. self.saveDir = "" # the name of the saved file
  15. self.flipYZ = 0 #convert from bl to right handed coord system
  16. #=======================================================================================================================
  17. # Vert =
  18. #=======================================================================================================================
  19. class Vert:
  20. def __init__(self):
  21. self.x = 0.0
  22. self.y = 0.0
  23. self.z = 0.0
  24. self.s = 0.0
  25. self.t = 0.0
  26. self.bonesNum = 0
  27. self.boneIds = [-1, -1, -1, -1]
  28. self.weights = [-1.0, -1.0, -1.0, -1.0]
  29. self.nextId = -1 # shows the next vertId. Is != -1 if the vert is problematic
  30. #=======================================================================================================================
  31. # Tri =
  32. #=======================================================================================================================
  33. class Tri:
  34. def __init__(self):
  35. self.vertIds = [-1, -1, -1]
  36. #=======================================================================================================================
  37. # getBlMeshFromBlObj =
  38. #=======================================================================================================================
  39. def getBlMeshFromBlObj(obj):
  40. if(obj.__class__.__name__ != "Blender Object"):
  41. raise RuntimeError("The given func param is not a \"Blender Object\" class but a \"" + obj.__class__.__name__ + "\"")
  42. if obj.getType() != "Mesh":
  43. raise RuntimeError("The obj \"" + obj.getName() + "\" must link to a mesh and not to a(n) " + obj.getType())
  44. mesh = obj.getData(0, 1)
  45. return mesh
  46. #=======================================================================================================================
  47. # updateAnkiVertsWithBoneWeights =
  48. #=======================================================================================================================
  49. def updateAnkiVertsWithBoneWeights(mesh, skeleton, ankiVerts):
  50. boneNames = skeleton.bones.keys()
  51. boneNames.sort()
  52. # init text
  53. ftxt = ""
  54. # link the vert groups to the bone ids
  55. vgroup2boneId = {} # we give the vgroup name and we get the bone's id in the skeleton
  56. vgroupNames = mesh.getVertGroupNames()
  57. for vgroupName in vgroupNames:
  58. boneId = -1
  59. for boneName in boneNames:
  60. if boneName == vgroupName:
  61. boneId = boneNames.index(boneName)
  62. break
  63. if boneId == -1:
  64. print("Vert group \"" + vgroupName + "\" cant link to a bone")
  65. vgroup2boneId[vgroupName] = boneId
  66. # for every non problematic vert do some shit
  67. for i in range(len(mesh.verts)):
  68. vert = mesh.verts[i]
  69. influences = mesh.getVertexInfluences(vert.index)
  70. influencesNum = 0
  71. sumw = 0.0
  72. # calc the influences num and the total weight (NOTE:we may have a vert group that doesnt connect to a bone)
  73. for influence in influences:
  74. vgroup = influence[0]
  75. weight = influence[1]
  76. if vgroup2boneId[vgroup] != -1:
  77. influencesNum = influencesNum + 1
  78. sumw = sumw + weight
  79. # a check
  80. if influencesNum > 4:
  81. raise RuntimeError("Cannot have more than 4 bones per vert")
  82. # write influences num
  83. ankiVerts[i].bonesNum = influencesNum
  84. for j in range(len(influences)):
  85. influence = influences[j]
  86. vgroup = influence[0]
  87. weight = influence[1]
  88. if vgroup2boneId[vgroup] != -1:
  89. # write bone id
  90. ankiVerts[i].boneIds[j] = vgroup2boneId[vgroup]
  91. # write weight for that bone
  92. ankiVerts[i].weights[j] = weight/sumw
  93. # end for all non problematic verts
  94. # for every canonical ankiVert fill the problematics
  95. for i in range(len(mesh.verts)):
  96. ankiVert = ankiVerts[i]
  97. cid = i # current id
  98. nid = ankiVert.nextId # next id
  99. if nid == -1:
  100. continue
  101. while nid != -1:
  102. # copy vert weight data
  103. ankiVerts[nid].bonesNum = ankiVerts[cid].bonesNum
  104. ankiVerts[nid].boneIds = deepcopy(ankiVerts[cid].boneIds)
  105. ankiVerts[nid].weights = deepcopy(ankiVerts[cid].weights)
  106. cid = nid
  107. nid = ankiVerts[nid].nextId
  108. #=======================================================================================================================
  109. # getAnkiMeshScript =
  110. #=======================================================================================================================
  111. def getAnkiMeshScript(mesh, skeleton, mtlName, flipYZ):
  112. # check verts number
  113. vertsNum = len(mesh.verts)
  114. if vertsNum < 3:
  115. raise RuntimeError("The mesh named \"" + mesh.name + "\" has insufficient vert num")
  116. # declare some vars
  117. ankiVerts = {}
  118. ankiTris = [Tri() for tri in range(len(mesh.faces))]
  119. hasUvs = 0
  120. # if it has UVs
  121. if mesh.faceUV:
  122. hasUvs = 1
  123. # for all faces
  124. for i in range(len(mesh.faces)):
  125. face = mesh.faces[i]
  126. if len(face.verts) != 3:
  127. raise RuntimeError("Only triangles are accepted")
  128. ankiTris[i] = Tri() # create new tri
  129. # for verts in the face
  130. for j in [0, 1, 2]:
  131. vertId = face.verts[j].index
  132. # vert does not exist
  133. if not ankiVerts.has_key(vertId):
  134. ankiVerts[vertId] = Vert()
  135. ankiVerts[vertId].x = mesh.verts[vertId].co.x
  136. ankiVerts[vertId].y = mesh.verts[vertId].co.y
  137. ankiVerts[vertId].z = mesh.verts[vertId].co.z
  138. ankiVerts[vertId].s = face.uv[j].x
  139. ankiVerts[vertId].t = face.uv[j].y
  140. ankiVerts[vertId].nextId = -1
  141. ankiTris[i].vertIds[j] = vertId
  142. else:
  143. prevId = -1
  144. while 1:
  145. # if in end of the list, create new vert and link list
  146. if vertId == -1:
  147. ankiVerts[vertsNum] = deepcopy(ankiVerts[prevId])
  148. ankiVerts[vertsNum].s = face.uv[j].x
  149. ankiVerts[vertsNum].t = face.uv[j].y
  150. ankiVerts[vertsNum].nextId = -1
  151. ankiVerts[prevId].nextId = vertsNum
  152. ankiTris[i].vertIds[j] = vertsNum
  153. vertsNum = vertsNum + 1
  154. break
  155. # a vert with the same UVs exists
  156. elif ankiVerts[vertId].s == face.uv[j].x and ankiVerts[vertId].t == face.uv[j].y:
  157. ankiTris[i].vertIds[j] = vertId
  158. break;
  159. # move to next node
  160. else:
  161. prevId = vertId
  162. vertId = ankiVerts[vertId].nextId
  163. # no UVs
  164. else:
  165. hasUvs = 0
  166. # set the verts
  167. for i in range(len(mesh.verts)):
  168. vert = mesh.verts[i]
  169. ankiVerts[i] = Vert()
  170. ankiVerts[i].x = vert.co.x
  171. ankiVerts[i].y = vert.co.y
  172. ankiVerts[i].z = vert.co.z
  173. # set the faces
  174. for i in range(len(mesh.faces)):
  175. face = mesh.faces[i]
  176. if len(face.verts) != 3:
  177. raise RuntimeError("Only triangles are accepted")
  178. for j in [0, 1, 2]:
  179. ankiTris[i].vertIds[j] = face.verts[j].index
  180. # write to ftxt
  181. # write mtl name
  182. ftxt = "\"" + mtlName + "\"\n"
  183. # write verts
  184. ftxt += str(len(ankiVerts)) + "\n"
  185. for i in range(len(ankiVerts)):
  186. ankiVert = ankiVerts[i]
  187. if flipYZ == 0:
  188. ftxt += str(ankiVert.x) + " " + str(ankiVert.y) + " " + str(ankiVert.z) + "\n"
  189. else:
  190. ftxt += str(ankiVert.x) + " " + str(ankiVert.z) + " " + str(-ankiVert.y) + "\n"
  191. # write the tris
  192. ftxt += str(len(ankiTris)) + "\n"
  193. for ankiTri in ankiTris:
  194. ftxt += str(ankiTri.vertIds[0]) + " " + str(ankiTri.vertIds[1]) + " " + str(ankiTri.vertIds[2]) + "\n"
  195. # write the UVs
  196. if hasUvs:
  197. ftxt += str(len(ankiVerts)) + "\n"
  198. for i in range(len(ankiVerts)):
  199. ftxt += str(ankiVerts[i].s) + " " + str(ankiVerts[i].t) + "\n"
  200. else:
  201. ftxt += "0\n"
  202. # write the vert weights
  203. if skeleton != None:
  204. updateAnkiVertsWithBoneWeights(mesh, skeleton, ankiVerts)
  205. ftxt += str(len(ankiVerts)) + "\n"
  206. for i in range(len(ankiVerts)):
  207. ankiVert = ankiVerts[i]
  208. ftxt += str(ankiVert.bonesNum) + "\n"
  209. for j in range(ankiVert.bonesNum):
  210. ftxt += str(ankiVert.boneIds[j]) + " " + str(ankiVert.weights[j]) + "\n"
  211. else:
  212. ftxt += "0\n"
  213. return ftxt
  214. #=======================================================================================================================
  215. # export =
  216. #=======================================================================================================================
  217. def export(meshInit):
  218. mesh = meshInit.blMesh
  219. skeleton = meshInit.blSkeleton
  220. #check if mesh is the correct class
  221. if mesh.__class__.__name__ != "Blender Mesh":
  222. raise RuntimeError("The given func param is not a \"Blender Mesh\" class but a \"" + mesh.__class__.__name__ + "\"")
  223. if skeleton != None:
  224. #check if skeleton is the correct class
  225. if(skeleton.__class__.__name__ != "Armature"):
  226. raise RuntimeError("The given func param is not a \"Armature\" class but a \"" + skeleton.__class__.__name__ + "\"")
  227. print("Trying to export mesh \"" + mesh.name + "\"")
  228. filename = os.path.abspath(meshInit.saveDir + mesh.name + ".mesh")
  229. file = open(filename, "w")
  230. file.write(getAnkiMeshScript(mesh, skeleton, meshInit.mtlName, meshInit.flipYZ))
  231. print("Mesh exported!! \"" + filename + "\"")