Skip to content
Snippets Groups Projects
object_skinify.py 30.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • # ##### BEGIN GPL LICENSE BLOCK #####
    #
    #  This program is free software; you can redistribute it and/or
    #  modify it under the terms of the GNU General Public License
    #  as published by the Free Software Foundation; either version 2
    #  of the License, or (at your option) any later version.
    #
    #  This program is distributed in the hope that it will be useful,
    #  but WITHOUT ANY WARRANTY; without even the implied warranty of
    #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    #  GNU General Public License for more details.
    #
    #  You should have received a copy of the GNU General Public License
    #  along with this program; if not, write to the Free Software Foundation,
    #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
    #
    # ##### END GPL LICENSE BLOCK #####
    
    bl_info = {
        "name": "Skinify Rig",
        "author": "Albert Makac (karab44)",
    
    lijenstina's avatar
    lijenstina committed
        "version": (0, 9, 1),
    
        "blender": (2, 7, 9),
    
        "location": "Properties > Bone > Skinify Rig (visible on pose mode only)",
        "description": "Creates a mesh object from selected bones",
        "warning": "",
    
        "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/"
                    "Py/Scripts/Object/Skinify",
    
        "category": "Object"}
    
    import bpy
    from bpy.props import (
            FloatProperty,
            IntProperty,
    
            BoolProperty,
            PointerProperty,
            )
    from bpy.types import (
            Operator,
            Panel,
            PropertyGroup,
            )
    from mathutils import (
            Vector,
            Euler,
    
            )
    from bpy.app.handlers import persistent
    
    # can the armature data properties group_prop and row be fetched directly from the rigify script?
    horse_data = \
            (1, 5), (2, 4), (3, 0), (4, 3), (5, 4), (1, 0), (1, 0), (7, 2), (8, 5), (9, 4), \
            (7, 2), (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), \
            (13, 6), (1, 4), (14, 6), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
    
    shark_data = \
            (1, 5), (2, 4), (1, 0), (3, 3), (4, 4), (5, 6), (6, 5), (7, 4), (6, 5), (7, 4), \
            (8, 3), (9, 4), (1, 0), (1, 6), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), \
            (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
    
    bird_data = \
            (1, 6), (2, 4), (1, 0), (3, 3), (4, 4), (1, 0), (1, 0), (6, 5), (8, 0), (7, 4), (6, 5), \
            (8, 0), (7, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (1, 0), (1, 0), \
            (13, 6), (14, 4), (1, 0), (8, 6), (1, 0), (1, 0), (1, 0), (14, 1),
    
    cat_data = \
            (1, 5), (2, 2), (2, 3), (3, 3), (4, 4), (5, 6), (6, 4), (7, 2), (8, 5), (9, 4), (7, 2), \
            (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (13, 3), (14, 4), \
            (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (16, 1),
    
    biped_data = \
            (1, 0), (1, 0), (1, 0), (3, 3), (4, 4), (1, 0), (1, 0), (7, 2), (8, 5), (9, 4), (7, 2), \
            (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (1, 0), (1, 0), \
            (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
    
    human_data = \
            (1, 5), (2, 2), (2, 3), (3, 3), (4, 4), (5, 6), (6, 4), (7, 2), (8, 5), (9, 4), (7, 2), \
            (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (1, 0), (1, 0), \
            (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
    
    wolf_data = \
            (1, 5), (2, 2), (2, 3), (3, 3), (4, 4), (5, 6), (6, 4), (7, 2), (8, 5), (9, 4), (7, 2), \
            (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (13, 6), (1, 0), \
            (13, 0), (13, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
    
    quadruped_data = \
            (1, 0), (2, 0), (2, 0), (3, 3), (4, 4), (5, 0), (6, 0), (7, 2), (8, 5), (9, 4), \
            (7, 2), (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (13, 6), \
            (1, 0), (13, 0), (13, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
    
    human_legacy_data = \
            (1, None), (1, None), (2, None), (1, None), (3, None), (3, None), (4, None), (5, None), \
            (6, None), (4, None), (5, None), (6, None), (7, None), (8, None), (9, None), (7, None), \
            (8, None), (9, None), (1, None), (1, None), (1, None), (1, None), (1, None), (1, None), \
            (1, None), (1, None), (1, None), (1, None),
    
    pitchipoy_data = \
            (1, None), (2, None), (2, None), (3, None), (4, None), (5, None), (6, None), (7, None), \
            (8, None), (9, None), (7, None), (8, None), (9, None), (10, None), (11, None), (12, None), \
            (10, None), (11, None), (12, None), (1, None), (1, None), (1, None), (1, None), (1, None), \
            (1, None), (1, None), (1, None), (1, None),
    
    rigify_data = horse_data, shark_data, bird_data, cat_data, biped_data, human_data, \
                  wolf_data, quadruped_data, human_legacy_data, pitchipoy_data
    
    lijenstina's avatar
    lijenstina committed
    
    # Skin.rig_type.ENUM
    # Skin.junction_dict['bname'].data[0] idx, data[1] idx + 1, data[2] thickness
    # NOTE each fragment contains section information about adequate bone
    # junctions idx and idx + 1 and these vertices' ids share common thickness
    
    class Skin(object):
    
    lijenstina's avatar
    lijenstina committed
    
    
        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
    
    lijenstina's avatar
    lijenstina committed
    
    
        def __init__(self, rig_type):
            self.rig_type = rig_type
            self.junctions_dict = dict()
    
    lijenstina's avatar
    lijenstina committed
    
        def fragment_create(self, bname, idx=None, thickness=0.0):
    
            data = []
            data.insert(0, idx)
    
    lijenstina's avatar
    lijenstina committed
    
            if idx is not None:
    
                data.insert(1, idx + 1)
            else:
    
    lijenstina's avatar
    lijenstina committed
                data.insert(1, None)
    
    
            self.junctions_dict[bname] = data
            self.junctions_dict[bname].append(thickness)
    
    lijenstina's avatar
    lijenstina committed
    
        # for the sake of code clarity
        def fragment_update(self, bname, idx=None, thickness=0.0):
    
            self.fragment_create(bname, idx, thickness)
    
    lijenstina's avatar
    lijenstina committed
    
    
    
    rig_type = Skin.Rig_type.OTHER
    
    lijenstina's avatar
    lijenstina committed
    
    
    # initialize properties
    def init_props():
    
        # additional check - this should be a rare case if the handler
    
        # wasn't removed for some reason and the add-on is not toggled on/off
    
        if hasattr(bpy.types.Scene, "skinify"):
            scn = bpy.context.scene.skinify
    
            scn.connect_mesh = False
            scn.connect_parents = False
            scn.generate_all = False
            scn.thickness = 0.8
            scn.finger_thickness = 0.25
            scn.apply_mod = True
            scn.parent_armature = True
            scn.sub_level = 1
    
    def identify_rig():
        if 'rigify_layers' not in bpy.context.object.data:
    
            return Skin.Rig_type.OTHER  # non recognized
    
    
        LEGACY_LAYERS_SIZE = 28
        layers = bpy.context.object.data['rigify_layers']
    
    
        for type, rig in enumerate(rigify_data):
    
    
            for props in layers:
                if len(layers) == LEGACY_LAYERS_SIZE and 'group_prop' not in props:
    
                    if props['row'] != rig[index][0] or rig[index][1] is not None:
    
                elif (props['row'] != rig[index][0]) or (props['group_prop'] != rig[index][1]):
    
                # SUCCESS if reaches the end
    
                if index == len(layers) - 1:
    
                    return Skin.Rig_type(type)
    
        return Skin.Rig_type.OTHER
    
    lijenstina's avatar
    lijenstina committed
    
    # prepares customizable ignore and thickness lists
    
    # edit these lists to suits your taste
    def prepare_lists(rig_type, finger_thickness):
    
    lijenstina's avatar
    lijenstina committed
    
    
        # 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']
    
        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',
    
                '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'
                ]
        wolf_ignore_list = [
                'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
                'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
                ]
        quad_ignore_list = [
                'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
                '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'
                ]
    
    lijenstina's avatar
    lijenstina committed
    
    
        # EXAMPLE THICKNESS
    
    lijenstina's avatar
    lijenstina committed
        # 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 = {}
    
    lijenstina's avatar
    lijenstina committed
        face_thickness = 0.20
        biped_thickness_dict = {}
        human_thickness_dict = {"face": face_thickness}
    
        wolf_thickness_dict = {}
    
    lijenstina's avatar
    lijenstina committed
        quad_thickness_dict = {}
        rigify_legacy_thickness_dict = {}
    
        pitchipoy_thickness_dict = {"face": face_thickness}
        other_thickness_dict = {}
    
    
    lijenstina's avatar
    lijenstina committed
        # combine lists depending on rig type
    
        ignore_list = common_ignore_list
    
    lijenstina's avatar
    lijenstina committed
        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 == Skin.Rig_type.SHARK:
    
            ignore_list = ignore_list + shark_ignore_list
    
            thickness_dict.update(shark_thickness_dict)
    
        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 == Skin.Rig_type.CAT:
    
            ignore_list = ignore_list + cat_ignore_list
    
            thickness_dict.update(cat_thickness_dict)
    
        elif rig_type == Skin.Rig_type.BIPED:
    
    lijenstina's avatar
    lijenstina committed
            ignore_list = ignore_list + biped_ignore_list
            thickness_dict.update(biped_thickness_dict)
    
        elif rig_type == Skin.Rig_type.HUMAN:
    
            ignore_list = ignore_list + human_ignore_list
    
    lijenstina's avatar
    lijenstina committed
            thickness_dict.update(human_thickness_dict)
    
            print("JUST A HUMAN AFTER ALL")
    
        elif rig_type == Skin.Rig_type.WOLF:
    
            ignore_list = ignore_list + wolf_ignore_list
    
            thickness_dict.update(wolf_thickness_dict)
    
        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 == Skin.Rig_type.LEGACY:
    
            ignore_list = ignore_list + rigify_legacy_ignore_list
    
            thickness_dict.update(rigify_legacy_thickness_dict)
    
        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 == Skin.Rig_type.OTHER:
    
            ignore_list = ignore_list + other_ignore_list
    
            thickness_dict.update(other_thickness_dict)
    
            print("rig non recognized...")
    
    lijenstina's avatar
    lijenstina committed
    
    
        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,
    
    lijenstina's avatar
    lijenstina committed
                       head_ornaments=False, generate_all=False, thickness=0.0, finger_thickness=0.0):
    
        This function adds vertices for all bones' heads and tails
    
    lijenstina's avatar
    lijenstina committed
    
    
        me = mesh
        verts = []
        edges = []
        idx = 0
    
    lijenstina's avatar
    lijenstina committed
    
    
        rig_type = identify_rig()
    
        skin = Skin(rig_type)
    
    lijenstina's avatar
    lijenstina committed
    
        # prepare the list
    
        ignore_list, thickness_dict = prepare_lists(skin.rig_type, finger_thickness)
    
    lijenstina's avatar
    lijenstina committed
    
        # create default junctions for all bones
        for b in bones:
    
            # set default thickness to all new junctions
    
    lijenstina's avatar
    lijenstina committed
            skin.fragment_create(bname=b.name, idx=None, thickness=thickness)
    
    
        # edge generator loop
    
    lijenstina's avatar
    lijenstina committed
        for b in bones:
    
            # look for rig's specific bones and their childs and set individual thickness
            for bname, thick in thickness_dict.items():
    
    lijenstina's avatar
    lijenstina committed
    
                if bname.lower() in b.name.lower():
                    skin.fragment_update(bname=b.name, idx=None, thickness=thick)
    
                    for c in b.children_recursive:
    
    lijenstina's avatar
    lijenstina committed
                        # update junctions with specific thickness
                        skin.fragment_update(bname=c.name, idx=None, thickness=thick)
    
    
            found = False
    
            for i in ignore_list:
    
                if i.lower() in b.name.lower():
    
                    found = True
                    break
    
            if found and generate_all is False:
                continue
    
    
            # fix for drawing rootbone and relationship lines
    
            if 'root' in b.name.lower() and generate_all is False:
                continue
    
            # ignore any head ornaments
    
            if head_ornaments is False:
    
                if b.parent is not None:
    
                    if 'head' in b.parent.name.lower() and not rig_type == Skin.Rig_type.HUMAN:
    
    karab44's avatar
    karab44 committed
                        continue
    
                    if 'face' in b.parent.name.lower() and rig_type == Skin.Rig_type.HUMAN:
    
                        continue
    
            if connect_parents:
                if b.parent is not None and b.parent.bone.select is True and b.bone.use_connect is False:
                    if 'root' in b.parent.name.lower() and generate_all is False:
                        continue
    
                    # ignore shoulder
    
                    if 'shoulder' in b.name.lower() and connect_mesh is True:
                        continue
    
                    # connect the upper arm directly with chest ommiting shoulders
    
                    if 'shoulder' in b.parent.name.lower() and connect_mesh is True:
                        vert1 = b.head
    
                        vert2 = b.parent.parent.tail
    
    
                    else:
                        vert1 = b.head
                        vert2 = b.parent.tail
    
                    verts.append(vert1)
                    verts.append(vert2)
                    edges.append([idx, idx + 1])
    
    lijenstina's avatar
    lijenstina committed
    
                    # also make list of edges made of gaps between the bones
    
                    for bname, data in skin.junctions_dict.items():
    
    lijenstina's avatar
    lijenstina committed
                        if b.name == bname:
    
                            skin.fragment_update(b.name, idx, data[2])
    
    lijenstina's avatar
    lijenstina committed
                            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 == 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))):
    
            vert1 = b.head
            vert2 = b.tail
            verts.append(vert1)
            verts.append(vert2)
    
    
    lijenstina's avatar
    lijenstina committed
            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])
    
    lijenstina's avatar
    lijenstina committed
    
    
        # Create mesh from given verts, faces
        me.from_pydata(verts, edges, [])
        # Update mesh with new data
    
    lijenstina's avatar
    lijenstina committed
        me.update()
    
        # set object scale exact as armature's scale
        shape_object.scale = scale
    
    
    # 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
    
    
    
    lijenstina's avatar
    lijenstina committed
    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)
    
    lijenstina's avatar
    lijenstina committed
    
    
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.select_all(action='DESELECT')
    
        # add skin modifier
        shape_object.modifiers.new("Skin", 'SKIN')
        bpy.ops.mesh.select_all(action='SELECT')
    
        override = bpy.context.copy()
        for area in bpy.context.screen.areas:
            if area.type == 'VIEW_3D':
                for region in area.regions:
                    if region.type == 'WINDOW':
                        override['area'] = area
                        override['region'] = region
                        override['edit_object'] = bpy.context.edit_object
                        override['scene'] = bpy.context.scene
                        override['active_object'] = shape_object
                        override['object'] = shape_object
                        override['modifier'] = bpy.context.object.modifiers
                        break
    
    
        # calculate optimal, normalized thickness for each segment
    
        bpy.ops.object.skin_root_mark(override)
    
    lijenstina's avatar
    lijenstina committed
    
    
        # select finger vertices and calculate optimal thickness for fingers to fix proportions
        # by default set fingers thickness to 25 percent of body thickness
    
    lijenstina's avatar
    lijenstina committed
        # make loose hands only for better topology
    
        if len(skin.junctions_dict.keys()) > 0:
    
            for bname, data in skin.junctions_dict.items():
    
    lijenstina's avatar
    lijenstina committed
                if data[0] is not None:
    
                    fragment_idx = list()
                    fragment_idx.append(data[0])
                    fragment_idx.append(data[1])
    
    lijenstina's avatar
    lijenstina committed
                    thickness = data[2]
    
                    select_vertices(shape_object, fragment_idx)
                    bpy.ops.transform.skin_resize(override,
    
    lijenstina's avatar
    lijenstina committed
                            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
    
        # bpy.ops.mesh.select_all(action='DESELECT')
    
        if connect_mesh:
            bpy.ops.object.mode_set(mode='EDIT')
            bpy.ops.mesh.select_all(action='DESELECT')
            bpy.ops.mesh.select_all(action='SELECT')
            bpy.ops.mesh.remove_doubles()
    
    lijenstina's avatar
    lijenstina committed
    
        # fix rigify and pitchipoy hands topology
    
        if connect_mesh and connect_parents and generate_all is False and \
    
    lijenstina's avatar
    lijenstina committed
                (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
    
    lijenstina's avatar
    lijenstina committed
    
    
            # left hand verts
            merge_idx = []
    
    lijenstina's avatar
    lijenstina committed
    
    
            if skin.rig_type == Skin.Rig_type.LEGACY:
                merge_idx = [8, 9, 14, 18, 23, 28]
    
    lijenstina's avatar
    lijenstina committed
    
    
            elif skin.rig_type == Skin.Rig_type.PITCHIPOY or skin.rig_type == Skin.Rig_type.HUMAN:
                merge_idx = [10, 11, 16, 20, 25, 30]
    
    lijenstina's avatar
    lijenstina committed
    
    
            select_vertices(shape_object, merge_idx)
            bpy.ops.mesh.merge(type='CENTER')
            bpy.ops.transform.skin_resize(override,
    
                    value=(corrective_thickness, corrective_thickness, corrective_thickness),
                    constraint_axis=(False, False, False), constraint_orientation='GLOBAL',
                    mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH',
                    proportional_size=1
                    )
    
            bpy.ops.mesh.select_all(action='DESELECT')
    
    
    lijenstina's avatar
    lijenstina committed
            # right hand verts
    
            if skin.rig_type == Skin.Rig_type.LEGACY:
                merge_idx = [31, 32, 37, 41, 46, 51]
    
    lijenstina's avatar
    lijenstina committed
    
    
            elif skin.rig_type == Skin.Rig_type.PITCHIPOY or skin.rig_type == Skin.Rig_type.HUMAN:
                merge_idx = [33, 34, 39, 43, 48, 53]
    
    lijenstina's avatar
    lijenstina committed
    
    
            select_vertices(shape_object, merge_idx)
            bpy.ops.mesh.merge(type='CENTER')
            bpy.ops.transform.skin_resize(override,
    
                    value=(corrective_thickness, corrective_thickness, corrective_thickness),
                    constraint_axis=(False, False, False), constraint_orientation='GLOBAL',
                    mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH',
                    proportional_size=1
                    )
    
    
            # making hands even more pretty
            bpy.ops.mesh.select_all(action='DESELECT')
            hands_idx = []  # left and right hand vertices
    
            if skin.rig_type == Skin.Rig_type.LEGACY:
    
                # hands_idx = [8, 33]  # L and R
    
                hands_idx = [7, 30]
    
    lijenstina's avatar
    lijenstina committed
    
    
            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 = [9, 32]
    
    lijenstina's avatar
    lijenstina committed
    
    
            select_vertices(shape_object, hands_idx)
            # change the thickness to make hands look less blocky and more sexy
            corrective_thickness = 0.7
            bpy.ops.transform.skin_resize(override,
    
                    value=(corrective_thickness, corrective_thickness, corrective_thickness),
                    constraint_axis=(False, False, False), constraint_orientation='GLOBAL',
                    mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH',
                    proportional_size=1
                    )
    
            bpy.ops.mesh.select_all(action='DESELECT')
    
        # todo optionally take root from rig's hip tail or head depending on scenario
    
        if skin.rig_type == Skin.Rig_type.LEGACY and selected_bones_num == total_bones_num:
    
            root_idx = [59]
    
    lijenstina's avatar
    lijenstina committed
        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:
    
        if len(root_idx) > 0:
            select_vertices(shape_object, root_idx)
            bpy.ops.object.skin_root_mark(override)
    
        # skin in edit mode
        # add Subsurf modifier
        shape_object.modifiers.new("Subsurf", 'SUBSURF')
        shape_object.modifiers["Subsurf"].levels = sub_level
        shape_object.modifiers["Subsurf"].render_levels = sub_level
    
        bpy.ops.object.mode_set(mode='OBJECT')
    
        # object mode apply all modifiers
        if apply_mod:
            bpy.ops.object.modifier_apply(override, apply_as='DATA', modifier="Skin")
            bpy.ops.object.modifier_apply(override, apply_as='DATA', modifier="Subsurf")
    
        return {'FINISHED'}
    
    
    def main(context):
        """
        This script will create a custome shape
        """
    
        # ### Check if selection is OK ###
        if len(context.selected_pose_bones) == 0 or \
    
                len(context.selected_objects) == 0 or \
    
                context.selected_objects[0].type != 'ARMATURE':
    
            return {'CANCELLED'}, "No bone selected or the Armature is hidden"
    
    
        scn = bpy.context.scene
    
        sknfy = scn.skinify
    
    
        # initialize the mesh object
        mesh_name = context.selected_objects[0].name + "_mesh"
        obj_name = context.selected_objects[0].name + "_object"
        armature_object = context.object
    
        origin = context.object.location
        bone_selection = context.selected_pose_bones
        oldLocation = None
        oldRotation = None
        oldScale = None
        armature_object = scn.objects.active
        armature_object.select = True
    
        old_pose_pos = armature_object.data.pose_position
        bpy.ops.object.mode_set(mode='OBJECT')
        oldLocation = Vector(armature_object.location)
        oldRotation = Euler(armature_object.rotation_euler)
        oldScale = Vector(armature_object.scale)
    
        bpy.ops.object.rotation_clear(clear_delta=False)
        bpy.ops.object.location_clear(clear_delta=False)
        bpy.ops.object.scale_clear(clear_delta=False)
    
        if sknfy.apply_mod and sknfy.parent_armature:
            armature_object.data.pose_position = 'REST'
    
    
        scale = bpy.context.object.scale
        size = bpy.context.object.dimensions[2]
    
        bpy.ops.object.mode_set(mode='OBJECT')
        bpy.ops.object.select_all(action='DESELECT')
    
        bpy.ops.object.add(type='MESH', enter_editmode=False, location=origin)
    
        # get the mesh object
        ob = scn.objects.active
        ob.name = obj_name
        me = ob.data
        me.name = mesh_name
    
        # this way we fit mesh and bvh with armature modifier correctly
    
    
        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
                                )
    
    lijenstina's avatar
    lijenstina committed
    
    
        generate_mesh(ob, size, sknfy.sub_level,
    
                      sknfy.connect_mesh, sknfy.connect_parents, sknfy.generate_all,
    
                      sknfy.apply_mod, skin, bone_selection)
    
    
        # parent mesh with armature only if modifiers are applied
    
        if sknfy.apply_mod and sknfy.parent_armature:
    
            bpy.ops.object.mode_set(mode='OBJECT')
            bpy.ops.object.select_all(action='DESELECT')
            ob.select = True
            armature_object.select = True
            scn.objects.active = armature_object
    
            bpy.ops.object.parent_set(type='ARMATURE_AUTO')
            armature_object.data.pose_position = old_pose_pos
            armature_object.select = False
        else:
            bpy.ops.object.mode_set(mode='OBJECT')
            ob.location = oldLocation
            ob.rotation_euler = oldRotation
            ob.scale = oldScale
            ob.select = False
            armature_object.select = True
            scn.objects.active = armature_object
    
        armature_object.location = oldLocation
        armature_object.rotation_euler = oldRotation
        armature_object.scale = oldScale
        bpy.ops.object.mode_set(mode='POSE')
    
        return {'FINISHED'}, me
    
    
    
    class BONE_OT_custom_shape(Operator):
    
        '''Creates a mesh object at the selected bones positions'''
        bl_idname = "object.skinify_rig"
        bl_label = "Skinify Rig"
        bl_options = {'REGISTER', 'UNDO'}
    
        @classmethod
        def poll(cls, context):
            return context.active_object is not None
    
        def execute(self, context):
            Mesh = main(context)
            if Mesh[0] == {'CANCELLED'}:
                self.report({'WARNING'}, Mesh[1])
                return {'CANCELLED'}
            else:
                self.report({'INFO'}, Mesh[1].name + " has been created")
    
                return {'FINISHED'}
    
    
    
    class BONE_PT_custom_shape(Panel):
    
        bl_space_type = 'PROPERTIES'
        bl_region_type = 'WINDOW'
        bl_context = "bone"
    
        bl_label = "Skinify Rig"
    
    
        @classmethod
        def poll(cls, context):
            ob = context.object
            return ob and ob.mode == 'POSE' and context.bone
    
        def draw(self, context):
            layout = self.layout
    
            scn = context.scene.skinify
    
    
            row = layout.row()
            row.operator("object.skinify_rig", text="Add Shape", icon='BONE_DATA')
    
    
            split = layout.split(percentage=0.3)
            split.label("Thickness:")
            split.prop(scn, "thickness", text="Body", icon='MOD_SKIN')
            split.prop(scn, "finger_thickness", text="Fingers", icon='HAND')
    
            split = layout.split(percentage=0.3)
            split.label("Mesh Density:")
            split.prop(scn, "sub_level", icon='MESH_ICOSPHERE')
    
    
            row = layout.row()
    
            row.prop(scn, "connect_mesh", icon='EDITMODE_HLT')
            row.prop(scn, "connect_parents", icon='CONSTRAINT_BONE')
    
            row = layout.row()
    
            row.prop(scn, "head_ornaments", icon='GROUP_BONE')
            row.prop(scn, "generate_all", icon='GROUP_BONE')
    
            row = layout.row()
    
            row.prop(scn, "apply_mod", icon='FILE_TICK')
    
            if scn.apply_mod:
                row = layout.row()
    
                row.prop(scn, "parent_armature", icon='POSE_HLT')
    
    
    # define the scene properties in a group - call them with context.scene.skinify
    class Skinify_Properties(PropertyGroup):
        sub_level = IntProperty(
                name="Sub level",
                min=0, max=4,
                default=1,
                description="Mesh density"
                )
        thickness = FloatProperty(
                name="Thickness",
                min=0.01,
                default=0.8,
                description="Adjust shape thickness"
                )
        finger_thickness = FloatProperty(
                name="Finger Thickness",
                min=0.01, max=1.0,
                default=0.25,
                description="Adjust finger thickness relative to body"
                )
        connect_mesh = BoolProperty(
                name="Solid Shape",
                default=False,
                description="Makes solid shape from bone chains"
                )
        connect_parents = BoolProperty(
                name="Fill Gaps",
                default=False,
                description="Fills the gaps between parented bones"
                )
        generate_all = BoolProperty(
                name="All Shapes",
                default=False,
                description="Generates shapes from all bones"
                )
        head_ornaments = BoolProperty(
                name="Head Ornaments",
                default=False,
                description="Includes head ornaments"
                )
        apply_mod = BoolProperty(
                name="Apply Modifiers",
                default=True,
                description="Applies Modifiers to mesh"
                )
        parent_armature = BoolProperty(
                name="Parent Armature",
                default=True,
                description="Applies mesh to Armature"
                )
    
    @persistent
    def startup_init(dummy):
        init_props()
    
    
    def register():
        bpy.utils.register_class(BONE_OT_custom_shape)
        bpy.utils.register_class(BONE_PT_custom_shape)
    
        bpy.utils.register_class(Skinify_Properties)
    
        bpy.types.Scene.skinify = PointerProperty(
                                        type=Skinify_Properties
                                        )
    
        # startup defaults
        bpy.app.handlers.load_post.append(startup_init)
    
    
    def unregister():
        bpy.utils.unregister_class(BONE_OT_custom_shape)
        bpy.utils.unregister_class(BONE_PT_custom_shape)
    
        bpy.utils.unregister_class(Skinify_Properties)
    
        # cleanup the handler
        bpy.app.handlers.load_post.remove(startup_init)
    
        del bpy.types.Scene.skinify
    
    
    
    if __name__ == "__main__":
        register()