diff options
author | “Humming-Owl” <“isaclien9752@gmail.com”> | 2023-08-28 19:31:42 -0400 |
---|---|---|
committer | “Humming-Owl” <“isaclien9752@gmail.com”> | 2023-08-28 19:31:42 -0400 |
commit | c16097bbf4230bc761e0f60be238b3362ef89c2b (patch) | |
tree | cf15177d733ead4499c5eed3148f5889ec8636fd /collada_bmd_bdl_import.py | |
download | blenxy-c16097bbf4230bc761e0f60be238b3362ef89c2b.tar.gz blenxy-c16097bbf4230bc761e0f60be238b3362ef89c2b.zip |
first commit on NotABug
Diffstat (limited to 'collada_bmd_bdl_import.py')
-rw-r--r-- | collada_bmd_bdl_import.py | 1067 |
1 files changed, 1067 insertions, 0 deletions
diff --git a/collada_bmd_bdl_import.py b/collada_bmd_bdl_import.py new file mode 100644 index 0000000..9187193 --- /dev/null +++ b/collada_bmd_bdl_import.py @@ -0,0 +1,1067 @@ +''' +just a DAE importer for SuperBMD DAEs +it loads the said DAE files with the default importer and the extension re-builds +the armature and mesh skinning. Done because Blender's 2.79 Collada importer +can miss armature stuff when importing (not all the time). It also lets +you select the type of pose you want in the armature when imported in Blender + +For my mental sanity this importer is only for SuperBMD files +Blender 2.79 deals with armature data on Collada files in an +extremely poorly way. Hopefully in the future I can make my own +Collada importer (epik project >:]). +''' + +import bpy, math, re +from mathutils import Matrix, Vector +from numpy import array +# ^ needed for converting an array to the correct type + +# Notes (AFAIK): + +# - bind shape matrix or BSM: matrix that tells the position of +# a mesh with respect to the armature origin so it is in the +# correct position for the bone it is assigned to. +# - inverse bind matrix or IBM: matrix located at the skin section of +# the collada file. The inverse of this matrix, the bind matrix, +# describes a bone position with respect to the armature origin when the +# meshes were assigned to the bones (shows on Blender in the +# bind_mat custom property for the bones of an armature). Bind matrices +# are the ones that show on Blender when loading a model. This matrix +# is the reference matrix used to when vertex weighted agaisnt a bone +# are moved by it in an animation +# - rest matrix: matrix found on the armature section of the collada +# file. It describes a bone position with respect to its parent bone +# at the moment the armature was edited (after the skinning) to get +# the model rest pose (t-pose stuff, for animation purposes). Shows +# on Blender on the rest_mat custom property for the bones of an armature. +# - All matrices read by Blender assume that the rotation system located in +# each 4x4 matrix is in the extrinsic Euler XYZ system + +################################################ +# get_bone_rest_matrix function +# function to: +# - calculate final rest matrix of a single bone +################################################ +def calculate_rest_matrix(rest_matrices, row_pos, bone_parenting_diagram): + + # acumulate transformation matrices while reading bone_parenting_diagram + # reading is done from the child bone to the ultimate parent bone + # final matrix will be initialized with current bone matrix from rest_matrices + final_matrix = rest_matrices[row_pos] + + # bone position on bone_parenting_diagram will be found in the next for loop + bone_parenting_pos = 0 + + for number in range(len(bone_parenting_diagram[row_pos])): + if (bone_parenting_diagram[row_pos][number] == 1): + bone_parenting_pos = number + break + + # with bone position on bone_parenting_diagram now it is time to read said + # array from bottom to top searching for each consecutive parent of said bone + # for a single bone, its parent is on the row the first "1" is found + # that is 1 position left from said bone in its row + + # example: + # | + # v + # bone 1 [1, 0, 0] row 1 + # bone 2 [0, 1, 0] row 2 + # bone 3 [0, 1 ,0] row 3 + # ^ + # | + # bone 3's parent is bone 1 + + # set 2 for loops to read the array from down to top and right to left >:] + # first loop on each for loop is skipped + + # variable to be used on inner loop + current_row = row_pos + + for parenting_pos in range(bone_parenting_pos, -1, -1): + + # first loop is skipped + if (parenting_pos == bone_parenting_pos): + continue + + for row_number in range(current_row, -1, -1): + + # first loop is skipped + if (row_number == row_pos): + continue + + # if element found is 0 go up one row + # if element found is 1 grab transformation matrix from rest_matrices + # perform multiplication, store row_number for the next inner loop + # then break out of the inner loop + if (bone_parenting_diagram[row_number][parenting_pos] == 0): + continue + + # bone_parenting_diagram[row_number][parenting_pos] == 1 returns True + else: + # acumulate transformation matrices + final_matrix = rest_matrices[row_number] * final_matrix + + # set new row for the next time the inner loop is executed + current_row = row_number + break + # + + return final_matrix + + +############################################### +############################################### +# read_bmd_bdl_collada function (MAIN FUNCTION) +# read superbmd/blenxy collada file +# used to import the collada file into Blender +############################################### +############################################### +def read_bmd_bdl_collada(context, filepath, debug_messages, show_armature_pose): + + ################### + # importing section + ################### + + # scene variable is always needed for something + scene = bpy.context.scene + + print("\nImporting Collada file used in BMD/BDL conversion...\n") + + # import collada in the standard way (blender importer, with no flags) + bpy.ops.wm.collada_import(filepath=filepath) + + print("\nCollada file (DAE) imported.") + + # initialize armature variable + armature = scene.objects.active + + # search the armature of the imported model + # Collada importer selects a child of the armature + # if only an armature is immported then the Collada + # importer selection is already the armature object + if (scene.objects.active.type != "ARMATURE"): + armature = scene.objects.active.parent + + # deselect everything + bpy.ops.object.select_all(action='DESELECT') + scene.objects.active = None + + # select and make active the armature + armature.select = True + scene.objects.active = armature + + + ########################### + # get armature data section + # from Collada file + ########################### + + print("Getting Armature data...") + + # open DAE file + f = open(filepath, 'r', encoding='utf-8') + + # I need the bind matrices and rest matrices from the armature bones + # also the bind shape matrix for each mesh + + # bind matrices are in the library_controllers section of the Collada file + # the inverse of them to be specific + # each mesh that is assigned to a vertex group (and consequently to a bone) + # has an inverse bind matrix for said bone. By inversing that matrix I get the + # bind matrix for the bone. This matrix is the one that should be applied to + # the bones on Blender. + + # rest matrices are in the library_visual_scenes section (matrices relative + # to each bone) and they are used in the animation process. + # If a bone does not have an inverse bind matrix on any mesh then + # the matrix used to position the bone on blender is pulled from here + # (the multiplication of the parent's rest matrices + the bone rest matrix). + + + ######################################################### + # infinite loop to get to the skin section of the collada + ######################################################### + while True: + line = f.readline() + + # find() returns -1 if it does not find anything on a string + # 0 is equivalent to False + if (line.find("<library_controllers>") + 1): + break + + + ############################# + # library_visual_scenes start + ############################# + + # controller_data will be used to store the mesh id, its bind shape matrix + # and bone names with their inverse bind pose matrix one mesh per row + # as follows: + + # row 1: mesh 1 id, BSM, bone 1 name, IBM of bone 1, bone 2 name, IBM of bone 2, ... + # row 2: mesh 2 id, BSM, bone a name, IBM of bone a, bone b name, IBM of bone b, ... + # . + # . + # . + + controller_data = [[]] + + # variables to keep track of the crontroller data length + i = -1 # for number of rows + + ####################################################### + # infinite loop to read the library_controllers section + ####################################################### + while True: + + line = f.readline() + + ######################### + # controller start + # the skin stuff for each + # mesh is defined there + ######################### + if (line.find("<controller") + 1): + + i = i + 1 + + # for the first mesh there is already a row in controller_data + if (i != 0): + controller_data.append([]) + + # get mesh id + mesh_id = re.search('id="(.+?)"', line).group(1) + controller_data[i].append(mesh_id) + + + ############################################## + # bind shape matrix from current mesh on row i + ############################################## + elif (line.find("<bind_shape_matrix>") + 1): + k = 0 + temp_rest_matrices = [] + + while (k < 4): + temp_array = [0, 0, 0, 0] + line = f.readline() + temp_array = line.split() + temp_array = [float(string) for string in temp_array] + l = 0 + + # fill temp_rest_matrices with a row + while (l < 4): + temp_rest_matrices.append(temp_array[l]) + l = l + 1 + + k = k + 1 + + # created "a" so the next matrix assignation is more readable + a = temp_rest_matrices + matrix = Matrix(( [a[0], a[1], a[2], a[3]], + [a[4], a[5], a[6], a[7]], + [a[8], a[9], a[10], a[11]], + [a[12], a[13], a[14], a[15]] + )) + + controller_data[i].append(matrix) + + + ######################################################## + # Name_array start + # the names of the bones to which the mesh was assigned + # to are here. Next to this section a few lines down the + # collada file there is the float_array section that has + # the inverse bind matrices for each bone of Name_array + ######################################################## + elif (line.find("<Name_array") + 1): + + # line were the bone names are + line = f.readline() + + # store bone names in temp_bones + temp_bones = [] + temp_bones = line.split() + + # skip the lines until float_array appears + while True: + line = f.readline() + if(line.find("<float_array") + 1): + break + + + ################### + # float_array start + ################### + + # read a 4x4 matrix, store it on temp_array_matrices + # then create a matrix and append the matrix with the + # respetive bone on controller_data[i] + + # to store matrices + temp_matrices = [] + + while True: + + # to store a matrix in the form of an array + temp_array_matrix = [] + + # will store the numbers as strings first on temp_array_matrix + k = 0 + while (k < 4): + line = f.readline() + + for number in line.split(): + temp_array_matrix.append(float(number)) + + k = k + 1 + + # created "a" so the next matrix assignation is more readable + a = temp_array_matrix + # build matrix + temp_matrix = Matrix(( [a[0], a[1], a[2], a[3]], + [a[4], a[5], a[6], a[7]], + [a[8], a[9], a[10],a[11]], + [a[12], a[13],a[14],a[15]] + )) + + # append matrix to temp_matrices + temp_matrices.append(temp_matrix) + + # after reading a matrix there is either + # - a blank line (another matrix starts next) + # - the end of float_array + + line = f.readline() + + if (line.find("</float_array>") + 1): + + # assign all bone names and its matrices to controller_data[i] + for k in range(len(temp_bones)): + controller_data[i].append(temp_bones[k]) + controller_data[i].append(temp_matrices[k]) + + # end of the Name_array and float_array section + break + + + #################################### + # end of library_controllers section + #################################### + elif (line.find("</library_controllers>") + 1): + break + + + ###################### + # controller data view + ###################### + + if (debug_messages): + for mesh in controller_data: + print("\nrow start") + for element in mesh: + print(element) + print() + + + ############################# + # library_visual_scenes start + ############################# + + line = f.readline() + + if not (line.find("<library_visual_scenes>") + 1): + print("DAE file contains an incompatible skin section structure for this importer. Check the file and try again.") + # end importer + return {'FINISHED'} + + line = f.readline() # contains visual_scene start + + # the next 2 lines have the armature name and tranformation matrix + # respectively, so I will store those separately from the bone's data + + line = f.readline() + armature_name = name = re.search('name="(.+?)"', line).group(1) + + line = f.readline() + armature_matrix_string = re.search('<matrix.*>(.*)</matrix>', line).group(1).split() + + # created "a" so the next matrix assignation is more readable + a = [float(string) for string in armature_matrix_string] + armature_matrix = Matrix(( [a[0], a[4], a[8], a[12]], + [a[1], a[5], a[9], a[13]], + [a[2], a[6], a[10], a[14]], + [a[3], a[7], a[11], a[15]] + )) + + ########### + # bone data + ########### + + # infinite loop to read this section + # all bone names will be stored in bones_array (as they show on the DAE file) + # all the matrices will be stored in rest_matrices (as they show on the DAE file) + # in this way the bones_array[i] bone rest matrix will be rest_matrices[i] + # to check tag start/end bone_tag_check will be used + # when <node> tag is reached the bone name related to that tag will be stored + # in bone_tag_check, then, if </node> tag is reached whatever bone is at the + # end of the bone_tag_check gets banished (like a stack) + # in this way I can get parent bones more easily for complex bone structures + + bones_array = [] + rest_matrices = [] + bone_tag_check = [] + + # the following array will be used to "draw" bone parenting + # each row will represent a bone and it will be filled with zeros from left to right + # until a position (position defined by the bone name position in bones_array) in + # which there will be a 1. In this way I can get a visual diagram to get the bone + # parenting for if I need to rebuild it later in the program + + bone_parenting_diagram = [[]] + + i = -1 # position of the last element on bones_array and rest_matrices + j = -1 # position of the last non_empty element on bone_tag_check + + # ^ initialized to -1 so the while loop from below is more convinient + + ######################################################### + # infinite loop section + # exit condition is when j = -1 (bone_tag_check is empty) + ######################################################### + + while True: + + line = f.readline() + + + ############ + # node start + ############ + if (line.find("<node ") + 1): + + # new bone, new matrix, new element in bone_tag_check + i = i + 1 + j = j + 1 + + # bone name + bone_name = re.search('name="(.+?)"', line).group(1) + bones_array.append(bone_name) + + # bone tag check + if (j >= len(bone_tag_check)): + bone_tag_check.append(bone_name) + else: + bone_tag_check[j] = bone_name + + # bone parenting diagram + if (i == 0): + bone_parenting_diagram[0] = [1] + else: + bone_parenting_diagram.append([]) + for k in range(j): + bone_parenting_diagram[i].append(0) + bone_parenting_diagram[i].append(1) + + + ################## + # matrix from node + ################## + elif (line.find("<matrix ") + 1): + + # store contents of matrix tag in an array + # transform array to float type then to a 4x4 matrix + bone_matrix_string_array = re.search('<matrix.*>(.*)</matrix>', line).group(1).split() + # created "a" so the next matrix assignation is more readable + a = [float(string) for string in bone_matrix_string_array] + bone_matrix = Matrix(([a[0], a[1], a[2], a[3]], + [a[4], a[5], a[6], a[7]], + [a[8], a[9], a[10], a[11]], + [a[12], a[13], a[14], a[15]] + )) + + rest_matrices.append(bone_matrix) + + + ########## + # node end + ########## + elif (line.find("</node>") + 1): + + # empty element bone_tag_check[j] + # decrease j by 1 + bone_tag_check[j] = "" + j = j - 1 + + + ##################################### + # what kind of DAE file I am reading? + ##################################### + else: + + # if the collada file contains a different structure than the usual + # for the armature section just end the importer and print text error text + print("DAE file contains an incompatible armature section structure for this importer. Check the file and try again.") + return {'FINISHED'} + + + ########### + # exit loop + ########### + if (j == -1): + break + + + ############## + # post process + + # finalize filling with zeros the rows of the bone_parenting_diagram + final_row_length = len(bone_tag_check) + + for row in bone_parenting_diagram: + for l in range(final_row_length - len(row)): + row.append(0) + + + #################### + # armature data view + #################### + + if (debug_messages): + print("Bone Names") + print() + print(bones_array) + print() + print("Rest Matrices") + print() + for rest_mat in rest_matrices: + print(rest_mat) + print() + print("Bone tag check (empty = good)") + print("number of columns must match the bone\nparenting diagram number of columns") + print() + print(bone_tag_check) + print() + print("Bone Parenting Diagram") + print() + for bone in bone_parenting_diagram: + print(bone) + print() + + + ########################## + # re-ordering data section + ########################## + + # this section is to fill 2 big and important arrays + # with all the data read from above + # - bind_matrices will hold all bone bind matrices for each bone (from the skin read) + # the inverse of the ones read to be specific. Will use the rest matrix from the + # armature section of the Collada if the bone does not have an IBM defined + # - final_rest_matrices will hold all bone rest matrices (armature section of + # the collada) matrices will be calculated with calculate_rest_matrix() + # function for each bone + + # the matrices will be in the ordered as the bone names on bone_names + + bind_matrices = [] + final_rest_matrices = [] + + # will be used to tell if a bone had defined a bind matrix and a rest matrix + # checking if a bone has a bind matrix is enought + has_bind_rest = [] + + # rest_matrices will be filled first and bind_matrices after + + ##################### + # final_rest_matrices + for i in range(len(bones_array)): + final_rest_matrices.append(calculate_rest_matrix(rest_matrices, i, bone_parenting_diagram)) + + ############### + # bind_matrices + for i in range(len(bones_array)): + + # loop through each element of the controller_data array + # to find all the bone's inverse bind matrices available + + bone_found = False + + for row in range(len(controller_data)): + for col in range(len(controller_data[row])): + if (bones_array[i] == controller_data[row][col]): + bind_matrices.append(controller_data[row][col + 1].inverted()) + has_bind_rest.append(True) + bone_found = True + break + + if (bone_found): + break + + # if a bone has not been found + # assign final rest matrix of said bone from final_rest_matrices + if not (bone_found): + bind_matrices.append(final_rest_matrices[i]) + has_bind_rest.append(False) + + + ############################################## + # bind, rest and final rest matrices data view + ############################################## + + if (debug_messages): + print("Bone Names") + print() + print(bones_array) + + print("\nBind Matrices\n") + for bind_mat in bind_matrices: + print(bind_mat) + + print("\nRest Matrices\n") + for rest_mat in rest_matrices: + print(rest_mat) + + print("\nFinal Rest Matrices\n") + for final_rest_mat in final_rest_matrices: + print(final_rest_mat) + + print("\nBone has both Matrices defined in Collada") + print() + print(bones_array) + print(has_bind_rest) + print() + + ########################## + # Rebuild armature section + ########################## + + # for this I will create a different armature object with all the bones + # and then link all the meshes from the imported model into this armature + # at the end the original imported armature will be deleted. + + print("Attempting re-build...") + + # change to object mode + bpy.ops.object.mode_set(mode='OBJECT') + + # deselect everything + bpy.ops.object.select_all(action='DESELECT') + scene.objects.active = None + + # store old armature object + armature_old = armature + + # create new armature + bpy.ops.object.armature_add(location=(0.0, 0.0, 0.0)) + armature = scene.objects.active + # change its name + armature.name = armature_old.name + "_new" + + # apply transformation matrix of armature old into new armature + armature.matrix_world = armature_matrix + + # enter edit mode + bpy.ops.object.mode_set(mode='EDIT') + + # create, apply transformation matrix and parent all bones in a + # column of the bone_parenting_diagram array (those at the "same parenting level") + # elements in bone_parenting_diagram are bone_parenting_diagram[i][j] + + for j in range(len(bone_parenting_diagram[0])): + for i in range(len(bone_parenting_diagram)): + + # first bone (position [0][0]) is already created + # I assume there is one bone that is the main bone + if (i == 0): + armature.data.edit_bones[0].name = bones_array[0] + armature.data.edit_bones[0].matrix = bind_matrices[0] + continue + + ################################## + # for the columns next to column 0 + ################################## + + # current position does not have a bone + if (bone_parenting_diagram[i][j] == 0): + continue + + # bone_parenting_diagram[i][j] == 1 returns True + # bone found, create it and assign name + bpy.ops.armature.bone_primitive_add(name = bones_array[i]) + + # find parent bone + # read the rows above the bone row to find its parent + # it is located at the column next to it (one column left) + # second argument in range() is -1 as I need a loop on k = 0 + + for k in range(i, -1, -1): + + # make k 1 less than i (row adjustment) + if (k >= i): + continue + + # start reading the column next to bone column (left column) + # Find the nearest 1 on said column (parent bone) + if (bone_parenting_diagram[k][j - 1] == 0): + continue + + # bone_parenting_diagram[k][j - 1] == 1 returns True --> parent bone found + + # assign bone parent + armature.data.edit_bones[bones_array[i]].parent = armature.data.edit_bones[bones_array[k]] + + # and more length to the bone (visual stuff, non-important) + armature.data.edit_bones[bones_array[i]].length = 10 + + # apply transformation matrix (bind matrix) + armature.data.edit_bones[bones_array[i]].matrix = bind_matrices[i] + + break + + # leave edit mode + bpy.ops.object.mode_set(mode='OBJECT') + + # assign bone custom properties only to bones that have + # both bind and rest matrices defined in the collada file + for i in range(len(bones_array)): + + # only check bones that have a bind matrix + if (has_bind_rest[i]): + + # Note: custom property matrices bind_mat and rest_mat are column written + + # create bind_mat custom property + # Note: by using Blender's FloatVectorProperty I get + # a custom property that can't be edited through GUI + # (API Defined, but it works well on the bind/rest_mat export) + # can be edited if exporting the model as DAE and then + # re-importing after closing and re-openning Blender + + # create rest_mat custom property (Blender's FloatVectorProperty) + # bpy.types.Bone.rest_mat = bpy.props.FloatVectorProperty( + # name="rest_mat", + # size=16, + # subtype="MATRIX" + # ) + + # bind matrix for bone + a = bind_matrices[i].transposed() + temp_array = [a[0][0], a[0][1], a[0][2], a[0][3], + a[1][0], a[1][1], a[1][2], a[1][3], + a[2][0], a[2][1], a[2][2], a[2][3], + a[3][0], a[3][1], a[3][2], a[3][3]] + + # convert temp_array to the right type with numpy + temp_array = array(temp_array, dtype = 'f') + + armature.data.bones[bones_array[i]]["bind_mat"] = temp_array + + # rest matrix for bone + a = rest_matrices[i].transposed() + temp_array = (a[0][0], a[0][1], a[0][2], a[0][3], + a[1][0], a[1][1], a[1][2], a[1][3], + a[2][0], a[2][1], a[2][2], a[2][3], + a[3][0], a[3][1], a[3][2], a[3][3]) + + # convert temp_array to the right type with numpy + temp_array = array(temp_array, dtype = 'f') + + armature.data.bones[bones_array[i]]["rest_mat"] = temp_array + + # Note: ^ if I don't convert temp_array to the right type and + # just assign it to the custom property, the exported matrices + # will contain weird data (no clue why that happens) + + + # armature has finished being built + print("Re-build done!") + + # assign meshes from old armature into new armature + # also reconect mesh's armature modifiers to the new armature + for child_mesh in armature_old.children: + child_mesh.parent = armature + child_mesh.modifiers["Armature"].object = armature + + # rename new armature with old armature's name + armature.name = armature_old.name + + + ############### + # final section + ############### + + # change back to object mode + bpy.ops.object.mode_set(mode='OBJECT') + + # deselect everything + bpy.ops.object.select_all(action='DESELECT') + scene.objects.active = None + + # select old armature + armature_old.select = True + scene.objects.active = armature_old + + # delete old armature + # it has been an honor :saluting_face: + bpy.ops.object.delete(use_global = False) + + # select new armature again + armature.select = True + scene.objects.active = armature + + ######################## + # apply pose to armature + ######################## + + print("Generating Armature poses...") + + # already have done the bind pose from the steps above + # so end the importer if the bind pose is the pose + # is the one wanted to be applied to the bones of the armature + if (show_armature_pose == "OPT_A"): + print("Bind Pose done!") + + # ^ if that is not the case then I have to build again the + # armature for the rest pose and the ignore rest pose + # but this time I have to deform manually the meshes so they are + # correct for the animation imports + + # Note: for some reason Mario's mesh has a deformation + # in the Hip zone after these vertex transformation + + # rest pose/ignore rest pose build + else: + + # change to edit mode + bpy.ops.object.mode_set(mode='EDIT') + + # build the rest pose/ignore rest pose on the bones + # of the armature (edit_bones, does not matter applying order) + # use final_rest_matrices for rest pose + # use rot_90_deg_mat for ignore rest pose + for i in range(len(armature.data.edit_bones)): + # + # get edit_bone + edit_bone = armature.data.edit_bones[i] + + if (show_armature_pose == "OPT_B"): + # rotate matrix (use ) + matrix = final_rest_matrices[i] + + elif (show_armature_pose == "OPT_C"): + # rotate matrix (all of the bones on ignore + # rest pose are in the same place) + matrix = Matrix(( [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + )) + + # assign matrix + edit_bone.matrix = matrix + # + + # deform mesh according to the rest pose + # of the armature a vertex at a time + for mesh in armature.children: + # + # get all vertex groups on the mesh + # its index and name + mesh_vertex_groups = [[], []] + for vertex_group in mesh.vertex_groups: + mesh_vertex_groups[0].append(vertex_group.index) + mesh_vertex_groups[1].append(vertex_group.name) + + for vertex in mesh.data.vertices: + # + # get vertex in armature's reference system + # and as a vector to be able to be multiplied by matrices + vertex_vector = Vector(([ vertex.co[0], + vertex.co[1], + vertex.co[2]] + )) + + # final vertex position variable + final_vertex_vector = Vector(([0, 0, 0])) + + # get the sum of the weights of this vertex on all + # vertex groups, the individual weights on each group + # and the vertex group index + # leave extra row on vertex_vertex_groups to store + # the vertex group names for later + weight_sum = 0 + vertex_vertex_groups = [[], [], []] + for vertex_group in vertex.groups: + vertex_vertex_groups[0].append(vertex_group.group) + vertex_vertex_groups[1].append(vertex_group.weight) + weight_sum = weight_sum + vertex_group.weight + + # iterate through each bone this vertex is linked to + # and apply the "skinning formula" AFAIK + + # this formula is as follows: + + # for a bone with a bind matrix (the matrix that describes the bone position + # without deforming the mesh, respect to armature) and a pose matrix (the + # position the bone has when the mesh deforms, with respect to armature) + # that influences a vertex with armature coordinates v and relative + # weight w for said bone the final position of the vertex for said + # bone movement is + + # v_final_for_bone = inverse_bind_mat * bone_pose_matrix * v * w + + # were w is the fraction of the weight of the vertex on the vertex group + # that is linked to the bone divided by the sum of all the weights the vertex + # has on all vertex groups it is assigned to + + # v_final_for_all_bones (the final trasnformation of the vertex that is + # got by all the bones that have influence on said vertex) is found by adding + # all individual v_final_for_bone vectors (one per bone, all the bones that + # influence the vertex) + + # find the bone names that affect this vertex + # and add those to the 3rd row of vertex_vertex_groups + # i will be used to go throught a mesh_vertex_groups row + # j will ve used to go through a vertex_vertex_groups row + i = 0 + j = 0 + while (j != len(vertex_vertex_groups[0])): + # + # when vertex group is located in mesh_vertex_groups + # append vertex group name in vertex_vertex_groups[2] (bone name) + # start reading mesh_vertex_groups again to search for the next bone + if (mesh_vertex_groups[0][i] == vertex_vertex_groups[0][j]): + vertex_vertex_groups[2].append(mesh_vertex_groups[1][i]) + i = 0 + j = j + 1 + + i = i + 1 + # + + # iterate through each bone that affects this vertex + for i in range(len(vertex_vertex_groups[0])): + # + # get bone and its index + bone = armature.data.bones[vertex_vertex_groups[2][i]] + bone_index = 0 + for j in range(len(armature.data.bones)): + if (vertex_vertex_groups[2][i] == armature.data.bones[j].name): + bone_index = j + break + + # do the proper multiplication of the influence of + # each bone on the vertex and sum the resulting vectors + # inverse_bind_mat is got from bind_matrices + # bone_pose_matrix will be the final_rest_matrices (for rest pose) + # or the identity matrix (for ignore rest pose) + bind_matrix = bind_matrices[bone_index] + inv_bind_matrix = bind_matrix.inverted() + + if (show_armature_pose == "OPT_B"): + + bone_pose_matrix = final_rest_matrices[bone_index] + + elif (show_armature_pose == "OPT_C"): + + bone_pose_matrix = Matrix(( [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + )) + + final_vertex_vector = final_vertex_vector + (inv_bind_matrix * bone_pose_matrix * vertex_vector * (vertex_vertex_groups[1][i] / weight_sum)) + # + + # apply final_vertex_vector to vertex + # in armature reference system + vertex.co = final_vertex_vector + # + # + + # scale down and rotate the armature + armature.scale = (0.01, 0.01, 0.01) + armature.rotation_euler[0] = math.radians(90) + + # apply scale to armature + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.transform_apply(location=False, rotation=True, scale=True) + + # apply scale to all meshes on armature + for mesh in armature.children: + mesh.select = True + scene.objects.active = mesh + bpy.ops.object.transform_apply(location=False, rotation=True, scale=True) + mesh.select = False + scene.objects.active = None + + # select armature again + armature.select = True + scene.objects.active = armature + + if (show_armature_pose == "OPT_B"): + print("Rest Pose done!") + elif (show_armature_pose == "OPT_C"): + print("Ignore Rest Pose done!") + + # end importer + 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_BMD_BDL_Collada(Operator, ExportHelper): + """Import a Collada file from SuperBMD (SuperBMD only)""" + bl_idname = "import_scene.collada_bmd_bdl" + bl_label = "Import Collada (from SuperBMD)" + + # ExportHelper mixin class uses this + filename_ext = ".dae" + + filter_glob = StringProperty( + default="*.dae", + options={'HIDDEN'}, + maxlen=255, + ) + + # option to show messages on terminal of the bone importing process + debug_messages = BoolProperty( name = "Display debug messages", + description = "Display armature data as it is read", + default = False, + ) + + # option to allow users define the pose of the armature that is shown on Blender + show_armature_pose = EnumProperty( + name="Armature Pose", + description="Pose to load on Blender for the imported armature", + items=( ('OPT_A', "Bind Pose", "Show Armature on Bind Pose"), + ('OPT_B', "Rest Pose", "Show Armature on Rest Pose"), + ('OPT_C', "Ignore Rest Pose", "Show Armature as on SMG when an empty BCK animation is applied to it. WARNNING: for complex armature models bone deformations on mesh wont be accurate (it is unknown why)")), + default='OPT_B', + ) + + def execute(self, context): + return read_bmd_bdl_collada(context, self.filepath, self.debug_messages, self.show_armature_pose) + + +# Only needed if you want to add into a dynamic menu +def menu_import_bmd_bdl_collada(self, context): + self.layout.operator(Import_BMD_BDL_Collada.bl_idname, text="Collada (from SuperBMD) (.dae)") + +bpy.utils.register_class(Import_BMD_BDL_Collada) +bpy.types.INFO_MT_file_import.append(menu_import_bmd_bdl_collada) + + +# test call +bpy.ops.import_scene.collada_bmd_bdl('INVOKE_DEFAULT') |