mesh.py 9.7 KB

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