collada_superbmd_import.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. import bpy, os
  2. from lxml import etree
  3. from . import collada_funcs
  4. from . import blender_funcs
  5. from . import math_funcs
  6. # Comments (spanish) (reescribir):
  7. # Bind Pose o Pose de Vínculo (aplica a huesos): es la posición (transformación) que tienen los huesos de un modelo en 3D cuando asignas los huesos a las mallas de dicho modelo. Cualquier otra posición deforma las mallas del modelo. A las matrices que llevan esta información se les denomina matriz de vínculo o bind matrix. En los archivos collada las matrices de vínculo (inversas) de los huesos nombrados en el elemento <Name_array> pueden obtenerse del elemento <float_array> (que viene después de <Name_array>) en el elemento <skin> de dicho archivo. Hay que invertir las matrices en esta sección para obtener las que se van a palicar a los huesos. Estas transformaciones son con respecto al sistema del objeto armadura (no son locales!).
  8. # Bind Shape Matrix o Matriz Forma de Vínculo (aplica a mallas): es la matriz de transformación que se le aplica a las mallas del modelo en 3D para que dichas mallas esten en la posición correcta para ser usadas por los huesos de dicho modelo. De otra forma, las mallas tienen una posición (transformación) original y estas matrices la modifican para entonces tener los vertices de dicha malla en la posición deseada para ser movidos por los huesos del modelo. Esta posición original creo que no tiene que ver con el proceso de vínculo (huesos con la malla) o con la pose de descanso, creo que es algo separado a ambos.
  9. # Rest Pose o Pose de Descanso (aplica a huesos): o pose de descanso, es la posición (transformación) asignada a los huesos luego de enlazar (vincular) los huesos a la malla del modelo en 3D. Por su descripción, puede ser igual o diferente a las transformaciones de hueso en la pose de vínculo. Es la transformación que se usa como base para las animaciones de esqueleto (no se usan para describir la posición de los huesos con las mallas!!!). Estas transformaciones son con respecto al origen del hueso padre (no son locales!).
  10. # Voy a suponer que las mallas con su matriz de forma de vinculo estan ya en la configuración para la pose de vinculo con los huesos (¿será por eso que se llama bind shape matrix?)
  11. # lo lógico es entonces:
  12. # - Cargar las mallas con el importador de collada de blender (Mallas con su BSM)
  13. # - Poner los huesos en su posición de vínculo con la información en las mallas (Huesos en su pose para empezar a deformar las mallas).
  14. # - Crear una pose de descanso para el esqueleto en el modo pose de blender (Huesos en su pose para animar las mallas)
  15. # - Almacenar la informacion de las poses de vínculo y descanso en las propieades de los huesos del esqueleto de blender
  16. # import_collada_superbmd() function
  17. # read superbmd/blenxy collada file
  18. # used to import the collada file into Blender
  19. def import_collada_superbmd(context, filepath):
  20. # scene variable is always needed for something
  21. scene = bpy.context.scene
  22. # make a temporal collada file with the "unit" and "up_axis" elements modified
  23. # and import that file (elements inside the assets element)
  24. # also fix the dumb Z:\ paths that happen when using wine
  25. xml = collada_funcs.check_collada(filepath)
  26. if (xml == None):
  27. blender_funcs.disp_msg("Collada file isn't well formatted")
  28. return {'FINISHED'}
  29. # get asset's unit element
  30. root = xml.getroot()
  31. # texture files path
  32. if (os.name == "posix"):
  33. for image in root.find("library_images"):
  34. tmp = image.find("init_from").text
  35. while (tmp[0] != '\\'):
  36. tmp = tmp[1:]
  37. image.find("init_from").text = tmp.replace("\\", "/")
  38. # save the new collada file and load it into blender
  39. xml.write(filepath + ".xml", pretty_print = True)
  40. bpy.ops.wm.collada_import(filepath = filepath + ".xml")
  41. # remove the temporal file
  42. os.remove(filepath + ".xml")
  43. ########################################
  44. # reconstruct the skeleton for bind pose
  45. # get the armature object and start doing math shidge
  46. armature = bpy.data.objects[-1] # last object imported
  47. # apply transform and select only the object
  48. blender_funcs.select_obj(scene, armature, False)
  49. # Showtime (bind pose, meshes are already in their correct position)
  50. # delete all bones in the armature
  51. bpy.ops.object.mode_set(mode = 'EDIT')
  52. bpy.ops.armature.select_all(action = 'SELECT')
  53. bpy.ops.armature.delete()
  54. # get the bones bind matrices
  55. # traverse through the <Name_array> and
  56. # <float_array> elements with the bone bind pose data
  57. bind_matrices = {}
  58. for controller in root.find("library_controllers").findall("controller"):
  59. bone_names = controller[0][1][0].text.split()
  60. bone_matrices = controller[0][2][0].text
  61. for i in range(0, len(bone_names)):
  62. mat = collada_funcs.get_text_mat4x4(bone_matrices, 16 * i).inverted()
  63. dict_entry = {bone_names[i] : mat}
  64. bind_matrices.update(dict_entry)
  65. # get the bones rest matrices and in parallel reconstruct the armature
  66. # traverse through the <node> elements in <visual_scene>
  67. rest_matrices = {}
  68. # search for the node named "skeleton_root" inside the <visual_scene>
  69. # element which is inside the <library_visual_scenes> element
  70. jnt_root_node = root.find("library_visual_scenes").find("visual_scene")
  71. for node in jnt_root_node:
  72. if (node.attrib["name"] == "skeleton_root"):
  73. jnt_root_node = node.find("node")
  74. break
  75. # get the bone rest pose information
  76. bpy.ops.object.mode_set(mode='EDIT')
  77. jnts_same_level = [jnt_root_node]
  78. is_first_jnt = True
  79. while (jnts_same_level != []):
  80. # get the joints for the next iteration
  81. next_jnts = []
  82. for jnt in jnts_same_level:
  83. for elem in jnt.getchildren():
  84. if (elem.tag == "node"):
  85. next_jnts.append(elem)
  86. # get the current joint's matrix information
  87. for jnt in jnts_same_level:
  88. jnt_name = jnt.attrib["name"]
  89. # create the bone
  90. bpy.ops.armature.bone_primitive_add(name = jnt_name)
  91. armature.data.edit_bones[jnt_name].length = 10
  92. mat = collada_funcs.get_text_mat4x4(jnt.find("matrix").text, 0)
  93. # check if this is the root joint of the skeleton
  94. if (is_first_jnt == False):
  95. parent_name = jnt.getparent().attrib["name"]
  96. # parent the bone
  97. armature.data.edit_bones[jnt_name].parent = armature.data.edit_bones[parent_name]
  98. # check if the current bone matrix is not zero filled
  99. if (mat.determinant() == 0):
  100. mat = math_funcs.get_id_mat4x4()
  101. # add the respective matrix to the dictionary and assign it to the bone
  102. rest_matrices.update({jnt_name : mat})
  103. jnts_same_level = next_jnts
  104. is_first_jnt = False
  105. # apply the bind matrices
  106. for i in range(0, len(armature.data.edit_bones)):
  107. bone = armature.data.edit_bones[i]
  108. if (bone.name in bind_matrices):
  109. bone.matrix = bind_matrices[bone.name]
  110. else:
  111. if (bone.parent != None):
  112. bone.matrix = bone.parent.matrix * rest_matrices[bone.name]
  113. else:
  114. bone.matrix = rest_matrices[bone.name]
  115. # get to pose mode
  116. bpy.ops.object.mode_set(mode='POSE')
  117. # set current pose to be rest pose and rest pose to be rest pose
  118. for bone in armature.pose.bones:
  119. if (bone.parent != None):
  120. bone.matrix = bone.parent.matrix * rest_matrices[bone.name]
  121. else:
  122. bone.matrix = rest_matrices[bone.name]
  123. # apply visual transform to all meshes
  124. bpy.ops.object.mode_set(mode='OBJECT')
  125. for child in armature.children:
  126. blender_funcs.select_obj(scene, child, False)
  127. bpy.ops.object.convert(target='MESH')
  128. # apply pose to rest pose
  129. blender_funcs.select_obj(scene, armature, False)
  130. bpy.ops.object.mode_set(mode='POSE')
  131. bpy.ops.pose.armature_apply()
  132. # reassign the armature modifiers to all meshes
  133. bpy.ops.object.mode_set(mode='OBJECT')
  134. for child in armature.children:
  135. blender_funcs.select_obj(scene, child, False)
  136. bpy.ops.object.modifier_add(type = 'ARMATURE')
  137. child.modifiers["Armature"].object = armature
  138. child.modifiers["Armature"].use_vertex_groups = True
  139. # scale down the model and apply transformations
  140. armature.scale = (0.01, 0.01, 0.01)
  141. blender_funcs.transf_apply_recurse(scene, armature, True, True, True)
  142. # done!
  143. blender_funcs.disp_msg("SuperBMD Collada file imported!")
  144. return {'FINISHED'}
  145. #################################################
  146. # Stuff down is for the menu appending
  147. # of the importer to work plus some setting stuff
  148. # comes from a Blender importer template
  149. # ExportHelper is a helper class, defines filename and
  150. # invoke() function which calls the file selector.
  151. from bpy_extras.io_utils import ExportHelper
  152. from bpy.props import StringProperty, BoolProperty, EnumProperty
  153. from bpy.types import Operator
  154. class import_superbmd_collada(Operator, ExportHelper):
  155. """Import a Collada file from SuperBMD (SuperBMD only)"""
  156. bl_idname = "import_scene.superbmd_collada"
  157. bl_label = "Import SuperBMD Collada (.DAE)"
  158. # ExportHelper mixin class uses this
  159. filename_ext = ".dae"
  160. filter_glob = StringProperty(default = "*.dae", options = {'HIDDEN'}, maxlen = 255)
  161. # execute function
  162. def execute(self, context):
  163. return import_collada_superbmd(context, self.filepath)
  164. # Only needed if you want to add into a dynamic menu
  165. def menu_import_superbmd_collada(self, context):
  166. self.layout.operator(import_superbmd_collada.bl_idname, text="SuperBMD Collada (.dae)")
  167. bpy.utils.register_class(import_superbmd_collada)
  168. bpy.types.INFO_MT_file_import.append(menu_import_superbmd_collada)
  169. # test call
  170. bpy.ops.import_scene.superbmd_collada('INVOKE_DEFAULT')