summaryrefslogtreecommitdiff
path: root/collada_bmd_bdl_import.py
diff options
context:
space:
mode:
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
commitc16097bbf4230bc761e0f60be238b3362ef89c2b (patch)
treecf15177d733ead4499c5eed3148f5889ec8636fd /collada_bmd_bdl_import.py
downloadblenxy-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.py1067
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')