summaryrefslogtreecommitdiff
path: root/collada_bmd_bdl_import.py
diff options
context:
space:
mode:
authorIsaac <isaclien9752@gmail.com>2024-08-14 22:48:16 -0400
committerIsaac <isaclien9752@gmail.com>2024-08-14 22:48:16 -0400
commit17fd2304270b2ef619cceb980b9f49cb656a6d13 (patch)
treedecd5572cf766c781c2bb6a604aa66155041cdb4 /collada_bmd_bdl_import.py
parentb907bcc414eb742f45e2a0c2ea0232256c226a92 (diff)
downloadblenxy-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.py1130
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')