# SPDX-License-Identifier: GPL-2.0-or-later

bl_info = {
    "name": "Skinify Rig",
    "author": "Albert Makac (karab44)",
    "version": (0, 11, 2),
    "blender": (2, 80, 0),
    "location": "Pose Mode > Sidebar > Create Tab",
    "description": "Creates a mesh object from selected bones",
    "warning": "Work in progress",
    "doc_url": "{BLENDER_MANUAL_URL}/addons/object/skinify.html",
    "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
from enum import Enum

# 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


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


class Idx_Store(object):
    def __init__(self, rig_type):
        self.rig_type = rig_type
        self.hand_r_merge = []
        self.hand_l_merge = []
        self.hands_pretty = []
        self.root = []

        if not self.rig_type == Rig_type.LEGACY and \
                not self.rig_type == Rig_type.HUMAN and \
                not self.rig_type == Rig_type.PITCHIPOY:
            return

        if self.rig_type == Rig_type.LEGACY:
            self.hand_l_merge = [7, 12, 16, 21, 26, 27]
            self.hand_r_merge = [30, 31, 36, 40, 45, 50]
            self.hands_pretty = [6, 29]
            self.root = [59]

        if self.rig_type == Rig_type.HUMAN or self.rig_type == Rig_type.PITCHIPOY:
            self.hand_l_merge = [9, 10, 15, 19, 24, 29]
            self.hand_r_merge = [32, 33, 37, 42, 47, 52]
            self.hands_pretty = [8, 31]
            self.root = [56]

    def get_all_idx(self):
        return self.hand_l_merge, self.hand_r_merge, self.hands_pretty, self.root

    def get_hand_l_merge_idx(self):
        return self.hand_l_merge

    def get_hand_r_merge_idx(self):
        return self.hand_r_merge

    def get_hands_pretty_idx(self):
        return self.hands_pretty

    def get_root_idx(self):
        return self.root


# 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


# selects vertices
def select_vertices(mesh_obj, idx):
    bpy.context.view_layer.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

    LEGACY_LAYERS_SIZE = 28
    layers = bpy.context.object.data['rigify_layers']

    for type, rig in enumerate(rigify_data):
        index = 0

        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:
                    break

            elif (props['row'] != rig[index][0]) or (props['group_prop'] != rig[index][1]):
                break

            # SUCCESS if reach the end
            if index == len(layers) - 1:
                return Rig_type(type)

            index = index + 1

    return 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
    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',
            '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'
            ]

    other_ignore_list = []

    ignore_list = common_ignore_list

    if rig_type == Rig_type.HORSE:
        ignore_list = ignore_list + horse_ignore_list
        print("RIDER OF THE APOCALYPSE")
    elif rig_type == Rig_type.SHARK:
        ignore_list = ignore_list + shark_ignore_list
        print("DEADLY JAWS")
    elif rig_type == Rig_type.BIRD:
        ignore_list = ignore_list + bird_ignore_list
        print("WINGS OF LIBERTY")
    elif rig_type == Rig_type.CAT:
        ignore_list = ignore_list + cat_ignore_list
        print("MEOW")
    elif rig_type == Rig_type.BIPED:
        ignore_list = ignore_list + biped_ignore_list
        print("HUMANOID")
    elif rig_type == Rig_type.HUMAN:
        ignore_list = ignore_list + human_ignore_list
        print("JUST A HUMAN AFTER ALL")
    elif rig_type == Rig_type.WOLF:
        ignore_list = ignore_list + wolf_ignore_list
        print("WHITE FANG")
    elif rig_type == Rig_type.QUAD:
        ignore_list = ignore_list + quad_ignore_list
        print("MYSTERIOUS CREATURE")
    elif rig_type == Rig_type.LEGACY:
        ignore_list = ignore_list + rigify_legacy_ignore_list
        print("LEGACY RIGIFY")
    elif rig_type == Rig_type.PITCHIPOY:
        ignore_list = ignore_list + pitchipoy_ignore_list
        print("PITCHIPOY")
    elif rig_type == Rig_type.OTHER:
        ignore_list = ignore_list + other_ignore_list
        print("rig non recognized...")

    return ignore_list


# 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):
    """
    This function adds vertices for all 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)

    # 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)

        found = False

        for i in ignore_list:
            if i 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 == Rig_type.HUMAN:
                    continue

                if 'face' in b.parent.name.lower() and rig_type == 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 omitting 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])

                # 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)

                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))):
                continue

        vert1 = b.head
        vert2 = b.tail
        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)

        idx = idx + 2

    # Create mesh from given verts, faces
    me.from_pydata(verts, edges, [])
    # Update mesh with new data
    me.update()

    # set object scale exact as armature's scale
    shape_object.scale = scale

    return alternate_scale_idx_list, rig_type


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=[]):
    """
    This function adds modifiers for generated edges
    """
    total_bones_num = bpy.context.selected_pose_bones_from_active_object
    selected_bones_num = len(bones)

    bpy.ops.object.mode_set(mode='EDIT')
    bpy.ops.mesh.select_all(action='DESELECT')

    # add skin modifier
    skin_modifier = shape_object.modifiers.new("Skin", 'SKIN')
    bpy.ops.mesh.select_all(action='SELECT')

    # calculate optimal thickness for defaults
    bpy.ops.object.skin_root_mark()
    bpy.ops.transform.skin_resize(
        value=(1 * thickness * (size / 10), 1 * thickness * (size / 10), 1 * thickness * (size / 10)),
        constraint_axis=(False, False, False),
        orient_type='GLOBAL',
        mirror=False,
        use_proportional_edit=False,
    )
    skin_modifier.use_smooth_shade = True
    skin_modifier.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(action='MARK')
        # by default set fingers thickness to 25 percent of body thickness
        bpy.ops.transform.skin_resize(
            value=(finger_thickness, finger_thickness, finger_thickness),
            constraint_axis=(False, False, False), orient_type='GLOBAL',
            mirror=False,
            use_proportional_edit=False,
        )
        # make loose hands only for better topology

    # 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()

    idx_store = Idx_Store(rig_type)

    # 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 \
            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 = idx_store.get_hand_l_merge_idx()

        select_vertices(shape_object, merge_idx)
        bpy.ops.mesh.merge(type='CENTER')
        bpy.ops.transform.skin_resize(
            value=(corrective_thickness, corrective_thickness, corrective_thickness),
            constraint_axis=(False, False, False), orient_type='GLOBAL',
            mirror=False,
            use_proportional_edit=False,
        )
        bpy.ops.mesh.select_all(action='DESELECT')

        # right hand verts
        merge_idx = idx_store.get_hand_r_merge_idx()

        select_vertices(shape_object, merge_idx)
        bpy.ops.mesh.merge(type='CENTER')
        bpy.ops.transform.skin_resize(
            value=(corrective_thickness, corrective_thickness, corrective_thickness),
            constraint_axis=(False, False, False), orient_type='GLOBAL',
            mirror=False,
            use_proportional_edit=False,
        )

        # making hands even more pretty
        bpy.ops.mesh.select_all(action='DESELECT')
        hands_idx = idx_store.get_hands_pretty_idx()

        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(
            value=(corrective_thickness, corrective_thickness, corrective_thickness),
            constraint_axis=(False, False, False), orient_type='GLOBAL',
            mirror=False,
            use_proportional_edit=False,
        )
        bpy.ops.mesh.select_all(action='DESELECT')

    # todo optionally take root from rig's hip tail or head depending on scenario

    root_idx = idx_store.get_root_idx()

    if selected_bones_num == total_bones_num:
        root_idx = [0]

    if len(root_idx) > 0:
        select_vertices(shape_object, root_idx)
        bpy.ops.object.skin_root_mark()
    # skin in edit mode
    # add Subsurf modifier
    subsurf_modifier = shape_object.modifiers.new("Subsurf", 'SUBSURF')
    subsurf_modifier.levels = sub_level
    subsurf_modifier.render_levels = sub_level

    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.context.view_layer.update()

    # object mode apply all modifiers
    if apply_mod:
        bpy.ops.object.modifier_apply(modifier=skin_modifier.name)
        bpy.ops.object.modifier_apply(modifier=subsurf_modifier.name)

    return {'FINISHED'}


def main(context):
    """
    This script will create a custom 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 = context.view_layer.objects.active
    armature_object.select_set(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 = context.view_layer.objects.active
    ob.name = obj_name
    me = ob.data
    me.name = mesh_name

    # 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,
                  sknfy.connect_mesh, sknfy.connect_parents, sknfy.generate_all,
                  sknfy.apply_mod, alternate_scale_idx_list, rig_type, 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_set(True)
        armature_object.select_set(True)
        bpy.context.view_layer.objects.active = armature_object

        bpy.ops.object.parent_set(type='ARMATURE_AUTO')
        armature_object.data.pose_position = old_pose_pos
        armature_object.select_set(False)
    else:
        bpy.ops.object.mode_set(mode='OBJECT')
        ob.location = oldLocation
        ob.rotation_euler = oldRotation
        ob.scale = oldScale
        ob.select_set(False)
        armature_object.select_set(True)
        context.view_layer.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):
    bl_idname = "object.skinify_rig"
    bl_label = "Skinify Rig"
    bl_description = "Creates a mesh object at the selected bones positions"
    bl_options = {'UNDO', 'INTERNAL'}

    @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 = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Create"
#    bl_context = "bone"
    bl_label = "Skinify Rig"
    bl_options = {'DEFAULT_CLOSED'}

    @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(factor=0.3)
        split.label(text="Thickness:")
        split.prop(scn, "thickness", text="Body", icon='MOD_SKIN')
        split.prop(scn, "finger_thickness", text="Fingers", icon='HAND')

        split = layout.split(factor=0.3)
        split.label(text="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"
            )


# startup defaults

@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()
