import bpy, math from . import blender_funcs # just a DAE exporter. # it was supposed to only rotate the meshes around the X axis by -90 degrees # but above that it now checks for armature and vertex group stuff # only exports the armature object selected # write_bmd_bdl_collada function # function to write custom collada file for # SuperBMD conversion def write_bmd_bdl_collada(context, filepath, triangulate): # this thing is always needed for stuff scene = bpy.context.scene # if nothing is selected end the exporter if (scene.objects.active == None or scene.objects.active.type != "ARMATURE"): if (scene.objects.active == None): blender_funcs.disp_msg("No Armature selected. Select one and try again.") else: blender_funcs.disp_msg("No Armature selected. Currently selecting: \"%s\"" % (scene.objects.active.name)) return {"FINISHED"} # get armature object armature = scene.objects.active print("\nArmature found: \"%s\"" % (armature.name)) # select it blender_funcs.select_obj(armature, False, "OBJECT") # check if the armature contains only mesh objects inside # unhide all objects for child in armature.children: child.hide = False if (child.type != "MESH"): blender_funcs.disp_msg("\"%s\": contains non-mesh object (%s)." % (armature.name, child.name)) return {"FINISHED"} # check if there is a single "root level" bone # SuperBMD/SMG needs a sigle root bone and the rest must be added as a child to that bone bone_with_no_parent_found = False for bone in armature.data.bones: # the root bone has been found if (bone.parent == None): if (bone_with_no_parent_found == False): bone_with_no_parent_found = True else: # second root bone? blender_funcs.disp_msg("\"%s\": has 2 or more root level bones (max is 1)." % (armature.name, child.name)) return {"FINISHED"} # check if all meshes have an armature modifier that is # assigned to the armature object and it is binded to # vertex groups if none of those are just create them, # I don't think it is bad to automate that for mesh in armature.children: # check if all meshes have an armature modifier has_armature_modifier = False has_multiple_armature_modifiers = False armature_modifier = None # iterate over the mesh modifiers i = 0 while (i < len(mesh.modifiers)): # check the existance of an armature modifier if (mesh.modifiers[i].type == "ARMATURE"): if (has_armature_modifier == False): has_armature_modifier = True armature_modifier = mesh.modifiers[i] # save current armature modifier else: # 2 armature modifiers? (remove duplicates) has_multiple_armature_modifiers = True mesh.modifiers.remove(mesh.modifiers[i]) i -= 1 i += 1 # notify the user about duplicate armature modifiers if (has_multiple_armature_modifiers == True): blender_funcs.disp_msg("\"%s\": has two or armature modifiers. Removing duplicates..." % (mesh.name)) # check if there is actually an armature modifier if (has_armature_modifier == False): blender_funcs.disp_msg("\"%s\": has no armature modifier. Adding one..." % (mesh.name)) armature_modifier = mesh.modifiers.new(name = armature.name, type = "ARMATURE") armature_modifier.object = armature armature_modifier.use_vertex_groups = True else: # ensure bind is to a vertex group and that the bind is to the actual parent armature if (armature_modifier.use_vertex_groups == False): blender_funcs.disp_msg("\"%s\": armature modifier wasn't binded to vertex groups" % (mesh.name)) armature_modifier.use_vertex_groups = True if (armature_modifier.object != armature): blender_funcs.disp_msg("\"%s\": armature modifier was binded to another armature object" % (mesh.name)) armature_modifier.object = armature; # check if all the vertex groups in each mesh correspond to the name of a skeleton bone bone_name_list = [] for bone in armature.data.bones: bone_name_list.append(bone.name) # vertex group check for mesh in armature.children: for v_group in mesh.vertex_groups: if (v_group.name not in bone_name_list): blender_funcs.disp_msg(("\"%s\": contains non-valid vertex group \"%s\"." % (mesh.name, v_group.name)) + " Unable to continue.") return {"FINISHED"} # vertex weight check for mesh in armature.children: for vertex in mesh.data.vertices: # check if the vertex is assigned to at least a single vertex group if (len(vertex.groups) == 0): blender_funcs.disp_msg(("\"%s\": contains unweighted vertices." % (mesh.name)) + " Unable to continue.") return {"FINISHED"} # check if vertex weights are normalized vertex_weight = 0 for group in vertex.groups: vertex_weight += group.weight # calling round because there seems to be floating point issues here if (round(vertex_weight, 5) > 1): blender_funcs.disp_msg(("\"%s\": contains non normalized weight in vertices." % (mesh.name)) + " Unable to continue.") return {"FINISHED"} # get the object names obj_name = armature.name child_names = [] for child in armature.children: child_names.append(child.name) # change to object view and make a copy of the armature object (with its children) blender_funcs.select_obj(armature, True, "OBJECT") bpy.ops.object.duplicate(linked = True) bpy.ops.object.make_single_user(type = "SELECTED_OBJECTS", object = True, obdata = True) old_armature = armature armature = scene.objects.active blender_funcs.select_obj(armature, False, "OBJECT") # apply the transformations to the armature armature.rotation_euler[0] = math.radians(-90) armature.scale = 100 * armature.scale bpy.ops.object.transform_apply(location = True, rotation = True, scale = True) # apply the transformations to the meshes inside the armature for i in range(0, len(armature.children)): blender_funcs.select_obj(armature.children[i], False, "OBJECT") bpy.ops.object.transform_apply(location = True, rotation = True, scale = True) blender_funcs.select_obj(armature, False, "OBJECT") # handle the object names so that they match the original model names armature.name = obj_name armature.data.name = obj_name for i in range(0, len(armature.children)): armature.children[i].name = child_names[i] armature.children[i].data.name = child_names[i] # delete all the rest_mat/bind_mat custom property matrices # they seem to alter exported models in a weird way (I would expect it is a Assimp thing) for bone in armature.data.bones: if ("bind_mat" in bone): bone.pop("bind_mat") if ("rest_mat" in bone): bone.pop("rest_mat") # export the object bpy.ops.wm.collada_export(filepath = filepath, use_blender_profile = False, selected = True, include_children = True, triangulate = triangulate) # delete the duplicate object blender_funcs.select_obj(armature, True, "OBJECT") bpy.ops.object.delete(use_global = False) # re-assign the original object names armature = old_armature armature.name = obj_name armature.data.name = obj_name for i in range(0, len(armature.children)): armature.children[i].name = child_names[i] armature.children[i].data.name = child_names[i] # done! blender_funcs.select_obj(armature, False, "OBJECT") blender_funcs.disp_msg("Armature \"%s\" exported!" % (armature.name)) return {"FINISHED"} # Stuff down is for the menu appending # of the exporter to work plus some setting stuff # comes from a Blender importer template from bpy_extras.io_utils import ExportHelper from bpy.props import StringProperty, BoolProperty, EnumProperty from bpy.types import Operator class export_superbmd_collada(Operator, ExportHelper): """Export a Collada file for SuperBMD (SuperBMD only)""" bl_idname = "export_scene.superbmd_collada" bl_label = "Export SuperBMD Collada (.DAE)" filename_ext = ".dae" filter_glob = StringProperty(default = "*.dae", options = {'HIDDEN'}, maxlen = 255) triangulate = BoolProperty(name = "Triangulate meshes", description = "Triangulate meshes inside the armature", default = False) def execute(self, context): return write_bmd_bdl_collada(context, self.filepath, self.triangulate) # Only needed if you want to add into a dynamic menu def menu_export_superbmd_collada(self, context): self.layout.operator(export_superbmd_collada.bl_idname, text="SuperBMD Collada (.dae)") # register func @bpy.app.handlers.persistent def register(dummy): try: bpy.utils.register_class(export_superbmd_collada) bpy.types.INFO_MT_file_export.append(menu_export_superbmd_collada) except: return # unregister func def unregister(): try: bpy.utils.unregister_class(export_superbmd_collada) bpy.types.INFO_MT_file_export.remove(menu_export_superbmd_collada) except: return