123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068 |
- '''
- 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')
|