collada_bmd_bdl_export.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. '''
  2. just a DAE exporter.
  3. it was supposed to only rotate the meshes around the X axis by -90 degrees
  4. but above that it now checks for armature and vertex group stuff
  5. only exports an armature object selection
  6. '''
  7. import bpy, math
  8. from .my_functions import *
  9. ####################################################
  10. # transform_apply_parent_child function
  11. # transform_apply the parent and its child meshes
  12. # selects the parent object at the end
  13. #
  14. # scene --> Blender scene in which the object exists
  15. # parent_object --> object that has child objects
  16. # to which the transform apply
  17. # operator will be applied
  18. # loc (bool) --> apply location
  19. # rot (bool) --> apply rotation
  20. # sca (bool) --> apply scaling
  21. ####################################################
  22. def transform_apply_parent_child(scene, parent_object, loc, rot, sca):
  23. #
  24. # parent object must be selected and active for the function to work
  25. bpy.ops.object.transform_apply(location = loc, rotation = rot, scale = sca)
  26. # armature child mesh scaling
  27. for child_mesh in parent_object.children:
  28. # empty selection and active object
  29. bpy.ops.object.select_all(action = 'DESELECT')
  30. scene.objects.active = None
  31. # select and activate child mesh
  32. child_mesh.select = True
  33. scene.objects.active = child_mesh
  34. # apply scale and rotation
  35. bpy.ops.object.transform_apply(location = loc, rotation = rot, scale = sca)
  36. # select parent_object
  37. bpy.ops.object.select_all(action='DESELECT')
  38. scene.objects.active = None
  39. parent_object.select = True
  40. scene.objects.active = parent_object
  41. #
  42. ################################################
  43. # write_bmd_bdl_collada function (MAIN FUNCTION)
  44. # function to write custom collada file for
  45. # SuperBMD conversion
  46. ################################################
  47. def write_bmd_bdl_collada(context, filepath, triangulate):
  48. #
  49. print("\nWriting Collada file for BMD/BDL conversion\n")
  50. # this thing is always needed for stuff
  51. scene = bpy.context.scene
  52. # if nothing is selected end the exporter
  53. if (scene.objects.active == None
  54. or
  55. scene.objects.active.type != 'ARMATURE'):
  56. error_string = "No Armature object selected. Select one and try again."
  57. print("\n### ERROR ###\n" + error_string + "\n### ERROR ###\n")
  58. show_message(error_string, "Error exporting collada file", 'ERROR')
  59. return {'FINISHED'}
  60. # get armature object
  61. armature = scene.objects.active
  62. print("Armature found: %s" % armature.name)
  63. print()
  64. # change to object view
  65. bpy.ops.object.mode_set(mode='OBJECT')
  66. # deselect everything (to be sure nothing weird happens)
  67. bpy.ops.object.select_all(action='DESELECT')
  68. scene.objects.active = None
  69. # re-select the armature object only
  70. armature.select = True
  71. scene.objects.active = armature
  72. # start mesh stuff
  73. print("\n### Mesh stuff ###")
  74. ##################################
  75. # check the meshes on the armature
  76. for object in armature.children:
  77. #
  78. if (object.type == "MESH"):
  79. #
  80. print()
  81. print("Mesh: \"" + object.name + "\"")
  82. print()
  83. ####################################
  84. # select the mesh and make it active
  85. object.select = True
  86. scene.objects.active = object
  87. # change mesh to object view (in case it isn't)
  88. bpy.ops.object.mode_set(mode='OBJECT')
  89. # reset scale, rotation and position of the mesh
  90. bpy.ops.object.transform_apply(location = True, rotation = True, scale = True)
  91. ##############################
  92. # check mesh armature modifier
  93. has_arm_mod = False
  94. for modifiers in object.modifiers:
  95. if (modifiers.name == "Armature"):
  96. has_arm_mod = True
  97. if (has_arm_mod):
  98. print("Mesh \"" + object.name + "\" has an Armature Modifier")
  99. if (object.modifiers["Armature"].object != armature):
  100. print("But not assigned to the right Armature Object. Re-assigning...")
  101. object.modifiers["Armature"].object = armature
  102. else:
  103. print("And it is assigned to the right Armature Object: \"" + armature.name + "\"")
  104. else:
  105. print("Mesh \"" + object.name + "\" does not have an Armature Modifier")
  106. print("Creating Modifier...")
  107. bpy.ops.object.modifier_add(type='ARMATURE')
  108. print("Assigning Modifier object to: \"" + armature.name + "\"")
  109. object.modifiers["Armature"].object = armature
  110. # check if the mesh contains vertex groups
  111. # if not, assign one that links to the main armature bone
  112. # and weight all vertex in the mesh to it, otherwise leave
  113. # the mesh in peace (mesh might not be weighted)
  114. # probably make a weight checker for each mesh so the exporter is complete
  115. is_weighted = True # variable used later
  116. if (object.vertex_groups.active == None):
  117. print("Mesh \"" + object.name + "\" does not have any Vertex Group assigned.")
  118. # change to edit mode (for vertex selection stuff)
  119. bpy.ops.object.mode_set(mode='EDIT')
  120. # get vertex group name (first armature bone name)
  121. ver_group_name = armature.data.bones[0].name
  122. print("Creating Vertex Group: \"" + ver_group_name + "\"")
  123. # create new vertex group for mesh
  124. object.vertex_groups.new(name = ver_group_name)
  125. print("Assigning all vertex in mesh to Vertex Group...")
  126. # select all vertex in mesh
  127. bpy.ops.mesh.select_all(action='SELECT')
  128. #get vertex group created
  129. ver_group = object.vertex_groups[ver_group_name]
  130. # set active vertex group in mesh to new vertex group created
  131. object.vertex_groups.active = ver_group
  132. # assign all vertex of the mesh to said vertex group
  133. bpy.ops.object.vertex_group_assign()
  134. print("Vertex assigned.")
  135. # go back to object mode
  136. bpy.ops.object.mode_set(mode='OBJECT')
  137. else:
  138. # if the mesh contains vertex groups then they might not
  139. # be correctly linked to the bones on an armature,
  140. # in other words, the mesh might contain a vertex group which name
  141. # isn't the name of a bone in the armature object
  142. # (this will throw a SuperBMD error)
  143. # Assimp.AssimpException: Error importing file: Collada: [DAE filename].dae - Invalid contents in element "n".
  144. if (len(object.vertex_groups) > len(armature.data.bones)):
  145. error_string = "Mesh has a vertex group that isn't related to any bone of the Armature.\nCheck the meshes and try again."
  146. print("\n### ERROR ###\n" + error_string + "\n### ERROR ###\n")
  147. show_message(error_string, "Error exporting collada file", 'ERROR')
  148. return {'FINISHED'}
  149. else:
  150. for ver_group in object.vertex_groups:
  151. valid_vertex_group = False
  152. for bone in armature.data.bones:
  153. if (ver_group.name == bone.name):
  154. valid_vertex_group = True
  155. break
  156. if (valid_vertex_group == False):
  157. error_string = "Mesh has a vertex group that isn't related to any bone of the Armature.\nCheck the meshes and try again."
  158. print("\n### ERROR ###\n" + error_string + "\n### ERROR ###\n")
  159. show_message(error_string, "Error exporting collada file", 'ERROR')
  160. return {'FINISHED'}
  161. # also the group might be valid but the vertex in
  162. # the mesh might not be weigthed in the vertex group
  163. # print message saying that the mesh might not be weighted
  164. # even if it has a vertex group (set is_weighted to False)
  165. ########################################
  166. # make loop to check for weights (later)
  167. ########################################
  168. print("Mesh \"" + object.name + "\" has a Vertex Group assigned.")
  169. print("Mesh might not be weighted.")
  170. is_weighted = False
  171. # deselect mesh at the end of each loop
  172. object.select = False
  173. scene.objects.active = None
  174. #
  175. #
  176. print("\n### Mesh stuff done ###")
  177. print()
  178. #################################################################
  179. # final part of the collada export
  180. # print messages on vertex weight given by the is_weight variable
  181. if (is_weighted):
  182. print("All meshes are weighted")
  183. else:
  184. print("All meshes are probably weighted")
  185. print("Finalizing export...\n")
  186. # select armature object and make it active
  187. # do the -90 degree rotation on X axis and scale it to 100
  188. # then do transform_apply, do transform_apply on the meshes
  189. # inside the armature and finally export the result into a Collada file
  190. # the 100 scaling is done because SuperBMD isn't able
  191. # to take the scaling factor on the DAE model and apply it to
  192. # the mesh on the BMD/BDL conversion (it just ignores it)
  193. armature.select = True
  194. scene.objects.active = armature
  195. # somehow the 2 statements before this comment change
  196. # the armature object into pose mode (???)
  197. # bpy.ops.object.mode_set(mode='OBJECT')
  198. # armature scale/rotation
  199. print("Rotating Armature -90 degrees on the X axis...")
  200. armature.rotation_euler[0] = math.radians(-90)
  201. print("Scaling to 100...")
  202. armature.scale = (100, 100, 100)
  203. # transform_apply the armature and its meshes
  204. transform_apply_parent_child(scene, armature, False, True, True)
  205. # export model (selection only)
  206. print("Exporting Model...")
  207. bpy.ops.wm.collada_export(filepath = filepath,
  208. use_blender_profile = False,
  209. selected = True,
  210. include_children = True,
  211. triangulate = triangulate)
  212. print("Collada file (DAE) exported.")
  213. # after export reset object rotation and scaling to original state
  214. # i.e. apply a 90 degree rotation on X axis and scale to 0.01
  215. # then do transform_apply
  216. # armature scale/rotation
  217. print("Reversing the -90 degrees rotation on the X axis...")
  218. armature.rotation_euler[0] = math.radians(90)
  219. print("Reversing the 100 scaling...")
  220. armature.scale = (0.01, 0.01, 0.01)
  221. # transform_apply the armature and its meshes
  222. transform_apply_parent_child(scene, armature, False, True, True)
  223. print("\n### Done! ###\n")
  224. return {'FINISHED'}
  225. #
  226. #################################################
  227. # Stuff down is for the menu appending
  228. # of the exporter to work plus some setting stuff
  229. # comes from a Blender importer template
  230. #################################################
  231. from bpy_extras.io_utils import ExportHelper
  232. from bpy.props import StringProperty, BoolProperty, EnumProperty
  233. from bpy.types import Operator
  234. class Export_BMD_BDL_Collada(Operator, ExportHelper):
  235. #
  236. """Save a Collada file for BMD/BDL conversion with SuperBMD"""
  237. bl_idname = "export_scene.collada_bmd_bdl"
  238. bl_label = "Export Collada (for BMD/BDL)"
  239. filename_ext = ".dae"
  240. filter_glob = StringProperty( default="*.dae",
  241. options={'HIDDEN'},
  242. maxlen=255,
  243. )
  244. triangulate = BoolProperty( name = "Triangulate meshes",
  245. description = "Triangulate meshes inside armatures",
  246. default = False,
  247. )
  248. def execute(self, context):
  249. return write_bmd_bdl_collada(context, self.filepath, self.triangulate)
  250. #
  251. def menu_export_bmd_bdl_collada(self, context):
  252. self.layout.operator(Export_BMD_BDL_Collada.bl_idname, text="Collada (for BMD/BDL) (.dae)")
  253. bpy.utils.register_class(Export_BMD_BDL_Collada)
  254. bpy.types.INFO_MT_file_export.append(menu_export_bmd_bdl_collada)
  255. # test call
  256. bpy.ops.export_scene.collada_bmd_bdl('INVOKE_DEFAULT')