diff options
author | Isaac <isaclien9752@gmail.com> | 2024-08-14 22:48:16 -0400 |
---|---|---|
committer | Isaac <isaclien9752@gmail.com> | 2024-08-14 22:48:16 -0400 |
commit | 17fd2304270b2ef619cceb980b9f49cb656a6d13 (patch) | |
tree | decd5572cf766c781c2bb6a604aa66155041cdb4 /collada_bmd_bdl_import.py | |
parent | b907bcc414eb742f45e2a0c2ea0232256c226a92 (diff) | |
download | blenxy-17fd2304270b2ef619cceb980b9f49cb656a6d13.tar.gz blenxy-17fd2304270b2ef619cceb980b9f49cb656a6d13.zip |
updated Collada importer/exporter for SuperBMD
Diffstat (limited to 'collada_bmd_bdl_import.py')
-rw-r--r-- | collada_bmd_bdl_import.py | 1130 |
1 files changed, 0 insertions, 1130 deletions
diff --git a/collada_bmd_bdl_import.py b/collada_bmd_bdl_import.py deleted file mode 100644 index 8894af6..0000000 --- a/collada_bmd_bdl_import.py +++ /dev/null @@ -1,1130 +0,0 @@ -''' -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]] - )).normalized() - - 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]] - )).normalized() - - # 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]] - )).normalized() - - ########### - # 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]] - )).normalized() - if (bone_matrix.determinant() == 0): - bone_matrix = Matrix(([1, 0, 0, 0], - [0, 1, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1])) - 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!") - - - # ~ #################### - # ~ # add model textures - # ~ #################### - - # ~ # will search for textures in the same place the DAE file is - # ~ # if they are not there the program won't do anything - - # ~ print("Getting Texture data...") - # ~ mat_json_path = filepath.split(".dae")[0] + "_materials.json" - # ~ print(mat_json_path) - - # ~ # open materials JSON - # ~ f = open(mat_json_path, 'r', encoding='utf-8') - - # ~ for i in range(len(armature.children)): - # ~ while True: - # ~ line = f.readline() - # ~ # material data start - # ~ if (line.find("\"Name\":") + 1): - - # ~ # get mat name - # ~ mat_name = re.search(': "(.+?)",', line).group(1) - - # ~ # find material along the - - - # ~ # get textures associated with this material - # ~ while True: - # ~ line = f.readline() - # ~ tex_name = "dummy" - # ~ if (line.find("\"TextureNames\":") + 1): - # ~ while (tex_name != ""): - # ~ tex_name = re.search('"(.+?)",', f.readline()).group(1) - # ~ break - # ~ break - # ~ break - - - # ~ for mesh in armature.children: - # ~ for i in range(len(mesh.data.materials)): - # ~ mesh.active_material_index = i - # ~ tex_slot = mesh.data.materials[i].texture_slots.add() - # ~ bpy.ops.texture.new() # create new texture - # ~ tex_slot.texture = bpy.data.textures[-1] - - # ~ while True: - - # ~ line = f.readline() - - # ~ ######################### - # ~ # controller start - # ~ # the skin stuff for each - # ~ # mesh is defined there - # ~ ######################### - # ~ if (line.find("<controller") + 1): - - # end importer - print("Done importing!") - 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') |