''' 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("") + 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("") + 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("") + 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("") + 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("") + 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('(.*)', 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 tag is reached the bone name related to that tag will be stored # in bone_tag_check, then, if 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("= 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("(.*)', 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("") + 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')