diff --git a/object_skinify.py b/object_skinify.py index 92e2a8edc87ab1fff2f1688948b4ca4df77e276d..c62466f23d770f6c3e9f7a763ef93ab7ae7b2cd3 100644 --- a/object_skinify.py +++ b/object_skinify.py @@ -19,8 +19,8 @@ bl_info = { "name": "Skinify Rig", "author": "Albert Makac (karab44)", - "version": (0, 8, 9), - "blender": (2, 7, 8), + "version": (0, 9, 0), + "blender": (2, 7, 9), "location": "Properties > Bone > Skinify Rig (visible on pose mode only)", "description": "Creates a mesh object from selected bones", "warning": "", @@ -104,23 +104,47 @@ pitchipoy_data = \ rigify_data = horse_data, shark_data, bird_data, cat_data, biped_data, human_data, \ wolf_data, quadruped_data, human_legacy_data, pitchipoy_data - -class Rig_type(Enum): - HORSE = 0 - SHARK = 1 - BIRD = 2 - CAT = 3 - BIPED = 4 - HUMAN = 5 - WOLF = 6 - QUAD = 7 - LEGACY = 8 - PITCHIPOY = 9 - OTHER = 10 - - -rig_type = Rig_type.OTHER - +#Skin.rig_type.ENUM +#Skin.junction_dict['bname'].data[0] idx, data[1] idx +1, data[2] thicnkess +#NOTE each fragment contains section information about adequate bone junctions idx and idx + 1 and these vertices' ids share common thickness +class Skin(object): + + class Rig_type(Enum): + HORSE = 0 + SHARK = 1 + BIRD = 2 + CAT = 3 + BIPED = 4 + HUMAN = 5 + WOLF = 6 + QUAD = 7 + LEGACY = 8 + PITCHIPOY = 9 + OTHER = 10 + + def __init__(self, rig_type): + self.rig_type = rig_type + self.junctions_dict = dict() + + + def fragment_create(self, bname, idx = None, thickness = 0.0): + data = [] + data.insert(0, idx) + + if idx != None: + data.insert(1, idx + 1) + else: + data.insert(1, None) + + self.junctions_dict[bname] = data + self.junctions_dict[bname].append(thickness) + + #for the sake of code clarity + def fragment_update(self, bname, idx = None, thickness = 0.0): + self.fragment_create(bname, idx, thickness) + + +rig_type = Skin.Rig_type.OTHER # initialize properties def init_props(): @@ -139,26 +163,9 @@ def init_props(): scn.sub_level = 1 -# selects vertices -def select_vertices(mesh_obj, idx): - bpy.context.scene.objects.active = mesh_obj - mode = mesh_obj.mode - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.mesh.select_all(action='DESELECT') - bpy.ops.object.mode_set(mode='OBJECT') - - for i in idx: - mesh_obj.data.vertices[i].select = True - - selectedVerts = [v.index for v in mesh_obj.data.vertices if v.select] - - bpy.ops.object.mode_set(mode=mode) - return selectedVerts - - def identify_rig(): if 'rigify_layers' not in bpy.context.object.data: - return Rig_type.OTHER # non recognized + return Skin.Rig_type.OTHER # non recognized LEGACY_LAYERS_SIZE = 28 layers = bpy.context.object.data['rigify_layers'] @@ -175,35 +182,33 @@ def identify_rig(): elif (props['row'] != rig[index][0]) or (props['group_prop'] != rig[index][1]): break - # SUCCESS if reach the end + # SUCCESS if reaches the end if index == len(layers) - 1: - return Rig_type(type) + return Skin.Rig_type(type) index = index + 1 - return Rig_type.OTHER - + return Skin.Rig_type.OTHER -def prepare_ignore_list(rig_type, bones): - # detect the head, face, hands, breast, heels or other exceptionary bones to exclusion or customization +#prepares customizable ignore and thickness lists +# edit these lists to suits your taste +def prepare_lists(rig_type, finger_thickness): + + + # EXAMPLE IGNORE LIST + # detect the head, face, hands, breast, heels or other exceptionary bones for exclusion or customization common_ignore_list = ['eye', 'heel', 'breast', 'root'] - - # edit these lists to suits your taste - horse_ignore_list = ['chest', 'belly', 'pelvis', 'jaw', 'nose', 'skull', 'ear.'] - shark_ignore_list = ['jaw'] - bird_ignore_list = [ 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow', 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue', 'beak' ] cat_ignore_list = [ - 'face', 'belly' 'pelvis.C', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow', + 'face', 'belly', 'pelvis.C', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow', 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue' ] biped_ignore_list = ['pelvis'] - human_ignore_list = [ 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow', 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue' @@ -217,84 +222,119 @@ def prepare_ignore_list(rig_type, bones): 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue' ] rigify_legacy_ignore_list = [] - pitchipoy_ignore_list = [ 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow', 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue' ] - other_ignore_list = [] - + + # EXAMPLE THICKNESS + # feel free to modify and customize the list by adding elements followed by comma common_thickness_dict = {"hand": common_finger_thickness, "head": common_head_thickness} + common_finger_thickness = finger_thickness + common_thickness_dict = {"hand": common_finger_thickness} + horse_thickness_dict = {} + shark_thickness_dict = {} + bird_thickness_dict = {} + cat_thickness_dict = {} + face_thickness = 0.20 + biped_thickness_dict = {} + human_thickness_dict = {"face": face_thickness} + wolf_thickness_dict = {} + quad_thickness_dict = {} + rigify_legacy_thickness_dict = {} + pitchipoy_thickness_dict = {"face": face_thickness} + other_thickness_dict = {} + + + #combine lists depending on rig type ignore_list = common_ignore_list - - if rig_type == Rig_type.HORSE: + thickness_dict = common_thickness_dict + if rig_type == Skin.Rig_type.HORSE: ignore_list = ignore_list + horse_ignore_list + thickness_dict.update(horse_thickness_dict) print("RIDER OF THE APOCALYPSE") - elif rig_type == Rig_type.SHARK: + elif rig_type == Skin.Rig_type.SHARK: ignore_list = ignore_list + shark_ignore_list + thickness_dict.update(shark_thickness_dict) print("DEADLY JAWS") - elif rig_type == Rig_type.BIRD: + elif rig_type == Skin.Rig_type.BIRD: ignore_list = ignore_list + bird_ignore_list + thickness_dict.update(bird_thickness_dict) print("WINGS OF LIBERTY") - elif rig_type == Rig_type.CAT: + elif rig_type == Skin.Rig_type.CAT: ignore_list = ignore_list + cat_ignore_list + thickness_dict.update(cat_thickness_dict) print("MEOW") - elif rig_type == Rig_type.BIPED: - ignore_list = ignore_list + biped_ignore_list + elif rig_type == Skin.Rig_type.BIPED: + ignore_list = ignore_list + biped_ignore_list + thickness_dict.update(biped_thickness_dict) print("HUMANOID") - elif rig_type == Rig_type.HUMAN: + elif rig_type == Skin.Rig_type.HUMAN: ignore_list = ignore_list + human_ignore_list + thickness_dict.update(human_thickness_dict) print("JUST A HUMAN AFTER ALL") - elif rig_type == Rig_type.WOLF: + elif rig_type == Skin.Rig_type.WOLF: ignore_list = ignore_list + wolf_ignore_list + thickness_dict.update(wolf_thickness_dict) print("WHITE FANG") - elif rig_type == Rig_type.QUAD: + elif rig_type == Skin.Rig_type.QUAD: ignore_list = ignore_list + quad_ignore_list + thickness_dict.update(quad_thickness_dict) print("MYSTERIOUS CREATURE") - elif rig_type == Rig_type.LEGACY: + elif rig_type == Skin.Rig_type.LEGACY: ignore_list = ignore_list + rigify_legacy_ignore_list + thickness_dict.update(rigify_legacy_thickness_dict) print("LEGACY RIGIFY") - elif rig_type == Rig_type.PITCHIPOY: + elif rig_type == Skin.Rig_type.PITCHIPOY: ignore_list = ignore_list + pitchipoy_ignore_list + thickness_dict.update(pitchipoy_thickness_dict) print("PITCHIPOY") - elif rig_type == Rig_type.OTHER: + elif rig_type == Skin.Rig_type.OTHER: ignore_list = ignore_list + other_ignore_list + thickness_dict.update(other_thickness_dict) print("rig non recognized...") - - return ignore_list + + return ignore_list, thickness_dict # generates edges from vertices used by skin modifier def generate_edges(mesh, shape_object, bones, scale, connect_mesh=False, connect_parents=False, - head_ornaments=False, generate_all=False): + head_ornaments=False, generate_all=False, thickness = 0.0, finger_thickness = 0.0): """ - This function adds vertices for all heads and tails + This function adds vertices for all bones' heads and tails """ - # scene preferences - - alternate_scale_list = [] - + me = mesh verts = [] edges = [] idx = 0 - alternate_scale_idx_list = list() - + rig_type = identify_rig() - ignore_list = prepare_ignore_list(rig_type, bones) - + skin = Skin(rig_type) + + # prepare the list + ignore_list, thickness_dict = prepare_lists(skin.rig_type, finger_thickness) + + #create default junctions for all bones + for b in bones: + # set default thickness to all new junctions + skin.fragment_create(bname = b.name, idx = None, thickness = thickness) + # edge generator loop - for b in bones: - # look for rig's hands and their childs - if 'hand' in b.name.lower(): - # prepare the list - for c in b.children_recursive: - alternate_scale_list.append(c.name) - + for b in bones: + # look for rig's specific bones and their childs and set individual thickness + for bname, thick in thickness_dict.items(): + + if bname.lower() in b.name.lower(): + skin.fragment_update(bname = b.name, idx = None, thickness = thick) + for c in b.children_recursive: + #update junctions with specific thickness + skin.fragment_update(bname = c.name, idx = None, thickness = thick) + found = False for i in ignore_list: - if i in b.name.lower(): + if i.lower() in b.name.lower(): found = True break @@ -309,10 +349,10 @@ def generate_edges(mesh, shape_object, bones, scale, connect_mesh=False, connect if head_ornaments is False: if b.parent is not None: - if 'head' in b.parent.name.lower() and not rig_type == Rig_type.HUMAN: + if 'head' in b.parent.name.lower() and not rig_type == Skin.Rig_type.HUMAN: continue - if 'face' in b.parent.name.lower() and rig_type == Rig_type.HUMAN: + if 'face' in b.parent.name.lower() and rig_type == Skin.Rig_type.HUMAN: continue if connect_parents: @@ -334,19 +374,22 @@ def generate_edges(mesh, shape_object, bones, scale, connect_mesh=False, connect verts.append(vert1) verts.append(vert2) edges.append([idx, idx + 1]) - - # also make list of edges made of gaps between the bones - for a in alternate_scale_list: - if b.name == a: - alternate_scale_idx_list.append(idx) - alternate_scale_idx_list.append(idx + 1) + + # also make list of edges made of gaps between the bones + for bname, data in skin.junctions_dict.items(): + if b.name == bname: + skin.fragment_update(b.name, idx, data[2]) + break + + # create new segment for new connections + skin.fragment_create(b.name + b.parent.name, idx, data[2]) idx = idx + 2 # for bvh free floating hips and hips correction for rigify and pitchipoy if ((generate_all is False and 'hip' in b.name.lower()) or - (generate_all is False and (b.name == 'hips' and rig_type == Rig_type.LEGACY) or - (b.name == 'spine' and rig_type == Rig_type.PITCHIPOY) or (b.name == 'spine' and - rig_type == Rig_type.HUMAN) or (b.name == 'spine' and rig_type == Rig_type.BIPED))): + (generate_all is False and (b.name == 'hips' and rig_type == Skin.Rig_type.LEGACY) or + (b.name == 'spine' and rig_type == Skin.Rig_type.PITCHIPOY) or (b.name == 'spine' and + rig_type == Skin.Rig_type.HUMAN) or (b.name == 'spine' and rig_type == Skin.Rig_type.BIPED))): continue vert1 = b.head @@ -354,35 +397,49 @@ def generate_edges(mesh, shape_object, bones, scale, connect_mesh=False, connect verts.append(vert1) verts.append(vert2) - edges.append([idx, idx + 1]) - - for a in alternate_scale_list: - if b.name == a: - alternate_scale_idx_list.append(idx) - alternate_scale_idx_list.append(idx + 1) + edges.append([idx, idx + 1]) + #insert idx to junctions and update + for bname, data in skin.junctions_dict.items(): + if b.name == bname: + skin.fragment_update(b.name, idx, data[2]) + idx = idx + 2 - + # Create mesh from given verts, faces me.from_pydata(verts, edges, []) # Update mesh with new data - me.update() - + me.update() # set object scale exact as armature's scale shape_object.scale = scale - return alternate_scale_idx_list, rig_type + return skin + + +# selects vertices +def select_vertices(mesh_obj, idx): + bpy.context.scene.objects.active = mesh_obj + mode = mesh_obj.mode + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='DESELECT') + bpy.ops.object.mode_set(mode='OBJECT') + + for i in idx: + mesh_obj.data.vertices[i].select = True + selectedVerts = [v.index for v in mesh_obj.data.vertices if v.select] -def generate_mesh(shape_object, size, thickness=0.8, finger_thickness=0.25, sub_level=1, - connect_mesh=False, connect_parents=False, generate_all=False, apply_mod=True, - alternate_scale_idx_list=[], rig_type=0, bones=[]): + bpy.ops.object.mode_set(mode=mode) + return selectedVerts + + +def generate_mesh(shape_object, size, sub_level=1, connect_mesh=False, connect_parents=False, generate_all=False, apply_mod=True, skin = None, bones=[]): """ This function adds modifiers for generated edges """ total_bones_num = len(bpy.context.object.pose.bones.keys()) selected_bones_num = len(bones) - + bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_all(action='DESELECT') @@ -404,31 +461,30 @@ def generate_mesh(shape_object, size, thickness=0.8, finger_thickness=0.25, sub_ override['modifier'] = bpy.context.object.modifiers break - # calculate optimal thickness for defaults + # calculate optimal, normalized thickness for each segment bpy.ops.object.skin_root_mark(override) - bpy.ops.transform.skin_resize(override, - value=(1 * thickness * (size / 10), 1 * thickness * (size / 10), 1 * thickness * (size / 10)), - constraint_axis=(False, False, False), constraint_orientation='GLOBAL', - mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', - proportional_size=1 - ) + + # select finger vertices and calculate optimal thickness for fingers to fix proportions + # by default set fingers thickness to 25 percent of body thickness + # make loose hands only for better topology + + if len(skin.junctions_dict.keys()) > 0: + for bname, data in skin.junctions_dict.items(): + if data[0] != None: + fragment_idx = list() + fragment_idx.append(data[0]) + fragment_idx.append(data[1]) + thickness = data[2] + select_vertices(shape_object, fragment_idx) + bpy.ops.transform.skin_resize(override, + value=(1 * thickness * (size / 10), 1 * thickness * (size / 10), 1 * thickness * (size / 10)), + constraint_axis=(False, False, False), constraint_orientation='GLOBAL', + mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', + proportional_size=1 + ) shape_object.modifiers["Skin"].use_smooth_shade = True shape_object.modifiers["Skin"].use_x_symmetry = True - # select finger vertices and calculate optimal thickness for fingers to fix proportions - if len(alternate_scale_idx_list) > 0: - select_vertices(shape_object, alternate_scale_idx_list) - - bpy.ops.object.skin_loose_mark_clear(override, action='MARK') - # by default set fingers thickness to 25 percent of body thickness - bpy.ops.transform.skin_resize(override, - value=(finger_thickness, finger_thickness, finger_thickness), - constraint_axis=(False, False, False), constraint_orientation='GLOBAL', - mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', - proportional_size=1 - ) - # make loose hands only for better topology - # bpy.ops.mesh.select_all(action='DESELECT') if connect_mesh: @@ -436,19 +492,24 @@ def generate_mesh(shape_object, size, thickness=0.8, finger_thickness=0.25, sub_ bpy.ops.mesh.select_all(action='DESELECT') bpy.ops.mesh.select_all(action='SELECT') bpy.ops.mesh.remove_doubles() - - # fix rigify and pitchipoy hands topology + + + # fix rigify and pitchipoy hands topology if connect_mesh and connect_parents and generate_all is False and \ - (rig_type == Rig_type.LEGACY or rig_type == Rig_type.PITCHIPOY or rig_type == Rig_type.HUMAN) and \ + (skin.rig_type == Skin.Rig_type.LEGACY or skin.rig_type == Skin.Rig_type.PITCHIPOY or skin.rig_type == Skin.Rig_type.HUMAN) and \ selected_bones_num == total_bones_num: # thickness will set palm vertex for both hands look pretty corrective_thickness = 2.5 + # left hand verts merge_idx = [] - if rig_type == Rig_type.LEGACY: - merge_idx = [7, 8, 13, 17, 22, 27] - elif rig_type == Rig_type.PITCHIPOY or rig_type == Rig_type.HUMAN: - merge_idx = [9, 14, 18, 23, 24, 29] + + if skin.rig_type == Skin.Rig_type.LEGACY: + merge_idx = [8, 9, 14, 18, 23, 28] + + elif skin.rig_type == Skin.Rig_type.PITCHIPOY or skin.rig_type == Skin.Rig_type.HUMAN: + merge_idx = [10, 11, 16, 20, 25, 30] + select_vertices(shape_object, merge_idx) bpy.ops.mesh.merge(type='CENTER') bpy.ops.transform.skin_resize(override, @@ -459,12 +520,13 @@ def generate_mesh(shape_object, size, thickness=0.8, finger_thickness=0.25, sub_ ) bpy.ops.mesh.select_all(action='DESELECT') - # right hand verts - if rig_type == Rig_type.LEGACY: - merge_idx = [30, 35, 39, 44, 45, 50] - elif rig_type == Rig_type.PITCHIPOY or rig_type == Rig_type.HUMAN: - merge_idx = [32, 37, 41, 46, 51, 52] - + # right hand verts + if skin.rig_type == Skin.Rig_type.LEGACY: + merge_idx = [31, 32, 37, 41, 46, 51] + + elif skin.rig_type == Skin.Rig_type.PITCHIPOY or skin.rig_type == Skin.Rig_type.HUMAN: + merge_idx = [33, 34, 39, 43, 48, 53] + select_vertices(shape_object, merge_idx) bpy.ops.mesh.merge(type='CENTER') bpy.ops.transform.skin_resize(override, @@ -477,12 +539,14 @@ def generate_mesh(shape_object, size, thickness=0.8, finger_thickness=0.25, sub_ # making hands even more pretty bpy.ops.mesh.select_all(action='DESELECT') hands_idx = [] # left and right hand vertices - if rig_type == Rig_type.LEGACY: + if skin.rig_type == Skin.Rig_type.LEGACY: # hands_idx = [8, 33] # L and R - hands_idx = [6, 29] - elif rig_type == Rig_type.PITCHIPOY or rig_type == Rig_type.HUMAN: + hands_idx = [7, 30] + + elif skin.rig_type == Skin.Rig_type.PITCHIPOY or skin.rig_type == Skin.Rig_type.HUMAN: # hands_idx = [10, 35] # L and R - hands_idx = [8, 31] + hands_idx = [9, 32] + select_vertices(shape_object, hands_idx) # change the thickness to make hands look less blocky and more sexy corrective_thickness = 0.7 @@ -497,9 +561,9 @@ def generate_mesh(shape_object, size, thickness=0.8, finger_thickness=0.25, sub_ # todo optionally take root from rig's hip tail or head depending on scenario root_idx = [] - if rig_type == Rig_type.LEGACY and selected_bones_num == total_bones_num: + if skin.rig_type == Skin.Rig_type.LEGACY and selected_bones_num == total_bones_num: root_idx = [59] - elif (rig_type == Rig_type.PITCHIPOY or rig_type == Rig_type.HUMAN) and selected_bones_num == total_bones_num: + elif (skin.rig_type == Skin.Rig_type.PITCHIPOY or skin.rig_type == Skin.Rig_type.HUMAN) and selected_bones_num == total_bones_num: root_idx = [56] elif selected_bones_num == total_bones_num: root_idx = [0] @@ -578,15 +642,15 @@ def main(context): # this way we fit mesh and bvh with armature modifier correctly - alternate_scale_idx_list, rig_type = generate_edges( - me, ob, bone_selection, scale, sknfy.connect_mesh, - sknfy.connect_parents, sknfy.head_ornaments, - sknfy.generate_all - ) - - generate_mesh(ob, size, sknfy.thickness, sknfy.finger_thickness, sknfy.sub_level, + skin = generate_edges( + me, ob, bone_selection, scale, sknfy.connect_mesh, + sknfy.connect_parents, sknfy.head_ornaments, + sknfy.generate_all, sknfy.thickness, sknfy.finger_thickness + ) + + generate_mesh(ob, size, sknfy.sub_level, sknfy.connect_mesh, sknfy.connect_parents, sknfy.generate_all, - sknfy.apply_mod, alternate_scale_idx_list, rig_type, bone_selection) + sknfy.apply_mod, skin, bone_selection) # parent mesh with armature only if modifiers are applied if sknfy.apply_mod and sknfy.parent_armature: