123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- import bpy, os
- from lxml import etree
- from . import collada_funcs
- from . import blender_funcs
- from . import math_funcs
- # Comments (spanish) (reescribir):
- # 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!).
- # 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.
- # 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!).
- # 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?)
- # lo lógico es entonces:
- # - Cargar las mallas con el importador de collada de blender (Mallas con su BSM)
- # - 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).
- # - Crear una pose de descanso para el esqueleto en el modo pose de blender (Huesos en su pose para animar las mallas)
- # - Almacenar la informacion de las poses de vínculo y descanso en las propieades de los huesos del esqueleto de blender
- # import_collada_superbmd() function
- # read superbmd/blenxy collada file
- # used to import the collada file into Blender
- def import_collada_superbmd(context, filepath):
-
- # scene variable is always needed for something
- scene = bpy.context.scene
-
- # make a temporal collada file with the "unit" and "up_axis" elements modified
- # and import that file (elements inside the assets element)
- # also fix the dumb Z:\ paths that happen when using wine
- xml = collada_funcs.check_collada(filepath)
- if (xml == None):
- blender_funcs.disp_msg("Collada file isn't well formatted")
- return {'FINISHED'}
-
- # get asset's unit element
- root = xml.getroot()
- unit_elem = root.find("asset").find("unit")
- unit_elem.attrib["meter"] = "0.01"
- # texture files path
- if (os.name == "posix"):
- for image in root.find("library_images"):
- tmp = image.find("init_from").text
- while (tmp[0] != '\\'):
- tmp = tmp[1:]
- image.find("init_from").text = tmp.replace("\\", "/")
-
- # save the new collada file and load it into blender
- xml.write(filepath + ".xml", pretty_print = True)
- bpy.ops.wm.collada_import(filepath = filepath + ".xml")
- # remove the temporal file
- os.remove(filepath + ".xml")
-
- ########################################
- # reconstruct the skeleton for bind pose
- # get the armature object and start doing math shidge
- armature = bpy.data.objects[-1] # last object imported
-
- # apply transform and select only the object
- blender_funcs.select_obj(scene, armature, False)
-
- # Showtime (bind pose, meshes are already in their correct position)
-
- # delete all bones in the armature
- bpy.ops.object.mode_set(mode = 'EDIT')
- bpy.ops.armature.select_all(action = 'SELECT')
- bpy.ops.armature.delete()
-
- # get the bones bind matrices
- # traverse through the <Name_array> and
- # <float_array> elements with the bone bind pose data
- bind_matrices = {}
- for controller in root.find("library_controllers").findall("controller"):
- bone_names = controller[0][1][0].text.split()
- bone_matrices = controller[0][2][0].text
- for i in range(0, len(bone_names)):
- mat = collada_funcs.get_text_mat4x4(bone_matrices, 16 * i).inverted()
- dict_entry = {bone_names[i] : mat}
- bind_matrices.update(dict_entry)
-
- # get the bones rest matrices and in parallel reconstruct the armature
- # traverse through the <node> elements in <visual_scene>
- rest_matrices = {}
- # search for the node named "skeleton_root" inside the <visual_scene>
- # element which is inside the <library_visual_scenes> element
- jnt_root_node = root.find("library_visual_scenes").find("visual_scene")
- for node in jnt_root_node:
- if (node.attrib["name"] == "skeleton_root"):
- jnt_root_node = node.find("node")
- break
-
- # get the bone rest pose information
- bpy.ops.object.mode_set(mode='EDIT')
- jnts_same_level = [jnt_root_node]
- is_first_jnt = True
- while (jnts_same_level != []):
-
- # get the joints for the next iteration
- next_jnts = []
- for jnt in jnts_same_level:
- for elem in jnt.getchildren():
- if (elem.tag == "node"):
- next_jnts.append(elem)
-
- # get the current joint's matrix information
- for jnt in jnts_same_level:
- jnt_name = jnt.attrib["name"]
- # create the bone
- bpy.ops.armature.bone_primitive_add(name = jnt_name)
- armature.data.edit_bones[jnt_name].length = 10
- mat = collada_funcs.get_text_mat4x4(jnt.find("matrix").text, 0)
- # check if this is the root joint of the skeleton
- if (is_first_jnt == False):
- parent_name = jnt.getparent().attrib["name"]
- # parent the bone
- armature.data.edit_bones[jnt_name].parent = armature.data.edit_bones[parent_name]
- # check if the current bone matrix is not zero filled
- if (mat.determinant() == 0):
- mat = math_funcs.get_id_mat4x4()
- # add the respective matrix to the dictionary and assign it to the bone
- rest_matrices.update({jnt_name : mat})
-
- jnts_same_level = next_jnts
- is_first_jnt = False
-
- # apply the bind matrices
- for i in range(0, len(armature.data.edit_bones)):
- bone = armature.data.edit_bones[i]
- if (bone.name in bind_matrices):
- bone.matrix = bind_matrices[bone.name]
- else:
- if (bone.parent != None):
- bone.matrix = bone.parent.matrix * rest_matrices[bone.name]
- else:
- bone.matrix = rest_matrices[bone.name]
-
- # get to pose mode
- bpy.ops.object.mode_set(mode='POSE')
-
- # set the "pose position" to be the rest position (for animations)
- # and keep the "rest position" as the bind position (cannot be altered)
- for bone in armature.pose.bones:
- if (bone.parent != None):
- bone.matrix = bone.parent.matrix * rest_matrices[bone.name]
- else:
- bone.matrix = rest_matrices[bone.name]
-
- # store the bind_mat and rest_mat custom properties on each bone
- bpy.ops.object.mode_set(mode='OBJECT')
- for bone in armature.data.bones:
- if ((bone.name in bind_matrices) and (bone.name in rest_matrices)):
- blender_funcs.set_bone_bind_mat(bone, bind_matrices[bone.name])
- else:
- blender_funcs.set_bone_bind_mat(bone, rest_matrices[bone.name])
- blender_funcs.set_bone_rest_mat(bone, rest_matrices[bone.name])
-
- # scale down the model and apply transformations
- bpy.ops.object.mode_set(mode='OBJECT')
- armature.scale = (0.01, 0.01, 0.01)
- bpy.ops.object.transforms_to_deltas(mode = 'ALL')
-
- # done!
- blender_funcs.disp_msg("SuperBMD Collada file imported!")
- return {'FINISHED'}
- #################################################
- # Stuff down is for the menu appending
- # of the importer to work plus some setting stuff
- # comes from a Blender importer template
- # ExportHelper is a helper class, defines filename and
- # invoke() function which calls the file selector.
- from bpy_extras.io_utils import ExportHelper
- from bpy.props import StringProperty, BoolProperty, EnumProperty
- from bpy.types import Operator
- class import_superbmd_collada(Operator, ExportHelper):
- """Import a Collada file from SuperBMD (SuperBMD only)"""
- bl_idname = "import_scene.superbmd_collada"
- bl_label = "Import SuperBMD Collada (.DAE)"
- # ExportHelper mixin class uses this
- filename_ext = ".dae"
- filter_glob = StringProperty(default = "*.dae", options = {'HIDDEN'}, maxlen = 255)
- # execute function
- def execute(self, context):
- return import_collada_superbmd(context, self.filepath)
- # Only needed if you want to add into a dynamic menu
- def menu_import_superbmd_collada(self, context):
- self.layout.operator(import_superbmd_collada.bl_idname, text="SuperBMD Collada (.dae)")
- bpy.utils.register_class(import_superbmd_collada)
- bpy.types.INFO_MT_file_import.append(menu_import_superbmd_collada)
- # test call
- bpy.ops.import_scene.superbmd_collada('INVOKE_DEFAULT')
|