collada_superbmd_import.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  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. unit_elem = root.find("asset").find("unit")
  32. unit_elem.attrib["meter"] = "0.01"
  33. # texture files path
  34. if (os.name == "posix"):
  35. for image in root.find("library_images"):
  36. tmp = image.find("init_from").text
  37. while (tmp[0] != '\\'):
  38. tmp = tmp[1:]
  39. image.find("init_from").text = tmp.replace("\\", "/")
  40. # save the new collada file and load it into blender
  41. xml.write(filepath + ".xml", pretty_print = True)
  42. bpy.ops.wm.collada_import(filepath = filepath + ".xml")
  43. # remove the temporal file
  44. os.remove(filepath + ".xml")
  45. ########################################
  46. # reconstruct the skeleton for bind pose
  47. # get the armature object and start doing math shidge
  48. armature = bpy.data.objects[-1] # last object imported
  49. # apply transform and select only the object
  50. blender_funcs.select_obj(scene, armature, False)
  51. # Showtime (bind pose, meshes are already in their correct position)
  52. # delete all bones in the armature
  53. bpy.ops.object.mode_set(mode = 'EDIT')
  54. bpy.ops.armature.select_all(action = 'SELECT')
  55. bpy.ops.armature.delete()
  56. # get the bones bind matrices
  57. # traverse through the <Name_array> and
  58. # <float_array> elements with the bone bind pose data
  59. bind_matrices = {}
  60. for controller in root.find("library_controllers").findall("controller"):
  61. bone_names = controller[0][1][0].text.split()
  62. bone_matrices = controller[0][2][0].text
  63. for i in range(0, len(bone_names)):
  64. mat = collada_funcs.get_text_mat4x4(bone_matrices, 16 * i).inverted()
  65. dict_entry = {bone_names[i] : mat}
  66. bind_matrices.update(dict_entry)
  67. # get the bones rest matrices and in parallel reconstruct the armature
  68. # traverse through the <node> elements in <visual_scene>
  69. rest_matrices = {}
  70. # search for the node named "skeleton_root" inside the <visual_scene>
  71. # element which is inside the <library_visual_scenes> element
  72. jnt_root_node = root.find("library_visual_scenes").find("visual_scene")
  73. for node in jnt_root_node:
  74. if (node.attrib["name"] == "skeleton_root"):
  75. jnt_root_node = node.find("node")
  76. break
  77. # get the bone rest pose information
  78. bpy.ops.object.mode_set(mode='EDIT')
  79. jnts_same_level = [jnt_root_node]
  80. is_first_jnt = True
  81. while (jnts_same_level != []):
  82. # get the joints for the next iteration
  83. next_jnts = []
  84. for jnt in jnts_same_level:
  85. for elem in jnt.getchildren():
  86. if (elem.tag == "node"):
  87. next_jnts.append(elem)
  88. # get the current joint's matrix information
  89. for jnt in jnts_same_level:
  90. jnt_name = jnt.attrib["name"]
  91. # create the bone
  92. bpy.ops.armature.bone_primitive_add(name = jnt_name)
  93. armature.data.edit_bones[jnt_name].length = 10
  94. mat = collada_funcs.get_text_mat4x4(jnt.find("matrix").text, 0)
  95. # check if this is the root joint of the skeleton
  96. if (is_first_jnt == False):
  97. parent_name = jnt.getparent().attrib["name"]
  98. # parent the bone
  99. armature.data.edit_bones[jnt_name].parent = armature.data.edit_bones[parent_name]
  100. # check if the current bone matrix is not zero filled
  101. if (mat.determinant() == 0):
  102. mat = math_funcs.get_id_mat4x4()
  103. # add the respective matrix to the dictionary and assign it to the bone
  104. rest_matrices.update({jnt_name : mat})
  105. jnts_same_level = next_jnts
  106. is_first_jnt = False
  107. # apply the bind matrices
  108. for i in range(0, len(armature.data.edit_bones)):
  109. bone = armature.data.edit_bones[i]
  110. if (bone.name in bind_matrices):
  111. bone.matrix = bind_matrices[bone.name]
  112. else:
  113. if (bone.parent != None):
  114. bone.matrix = bone.parent.matrix * rest_matrices[bone.name]
  115. else:
  116. bone.matrix = rest_matrices[bone.name]
  117. # get to pose mode
  118. bpy.ops.object.mode_set(mode='POSE')
  119. # set the "pose position" to be the rest position (for animations)
  120. # and keep the "rest position" as the bind position (cannot be altered)
  121. for bone in armature.pose.bones:
  122. if (bone.parent != None):
  123. bone.matrix = bone.parent.matrix * rest_matrices[bone.name]
  124. else:
  125. bone.matrix = rest_matrices[bone.name]
  126. # store the bind_mat and rest_mat custom properties on each bone
  127. bpy.ops.object.mode_set(mode='OBJECT')
  128. for bone in armature.data.bones:
  129. if ((bone.name in bind_matrices) and (bone.name in rest_matrices)):
  130. blender_funcs.set_bone_bind_mat(bone, bind_matrices[bone.name])
  131. else:
  132. blender_funcs.set_bone_bind_mat(bone, rest_matrices[bone.name])
  133. blender_funcs.set_bone_rest_mat(bone, rest_matrices[bone.name])
  134. # scale down the model and apply transformations
  135. bpy.ops.object.mode_set(mode='OBJECT')
  136. armature.scale = (0.01, 0.01, 0.01)
  137. bpy.ops.object.transforms_to_deltas(mode = 'ALL')
  138. # done!
  139. blender_funcs.disp_msg("SuperBMD Collada file imported!")
  140. return {'FINISHED'}
  141. #################################################
  142. # Stuff down is for the menu appending
  143. # of the importer to work plus some setting stuff
  144. # comes from a Blender importer template
  145. # ExportHelper is a helper class, defines filename and
  146. # invoke() function which calls the file selector.
  147. from bpy_extras.io_utils import ExportHelper
  148. from bpy.props import StringProperty, BoolProperty, EnumProperty
  149. from bpy.types import Operator
  150. class import_superbmd_collada(Operator, ExportHelper):
  151. """Import a Collada file from SuperBMD (SuperBMD only)"""
  152. bl_idname = "import_scene.superbmd_collada"
  153. bl_label = "Import SuperBMD Collada (.DAE)"
  154. # ExportHelper mixin class uses this
  155. filename_ext = ".dae"
  156. filter_glob = StringProperty(default = "*.dae", options = {'HIDDEN'}, maxlen = 255)
  157. # execute function
  158. def execute(self, context):
  159. return import_collada_superbmd(context, self.filepath)
  160. # Only needed if you want to add into a dynamic menu
  161. def menu_import_superbmd_collada(self, context):
  162. self.layout.operator(import_superbmd_collada.bl_idname, text="SuperBMD Collada (.dae)")
  163. bpy.utils.register_class(import_superbmd_collada)
  164. bpy.types.INFO_MT_file_import.append(menu_import_superbmd_collada)
  165. # test call
  166. bpy.ops.import_scene.superbmd_collada('INVOKE_DEFAULT')