import bpy, bmesh, mathutils, math, bpy_extras, ast, os, time, sys
from bpy.types import (Operator, Menu, Panel, UIList, PropertyGroup)
from bpy.props import *
from mathutils import *
from math import *
from operator import itemgetter
from . import auto_rig_datas as ard
from . import auto_rig_reset, rig_functions, mannequin_coords, mannequin_coords_tpose, auto_rig_prefs
from .utils import *
from .define import *
import bpy.utils.previews# custom icons
import gpu, bgl# draw
from gpu_extras.batch import *
import gpu_extras
from bpy.app.handlers import persistent
import requests# check for updates online

tnzpv = False

    
##########################  CLASSES  #########################


class ARP_eyelids_borders_data:
    left_borders = None
    right_borders = None

eyelids_borders_data = ARP_eyelids_borders_data()


class ARP_OT_set_eyelids_borders(Operator):
    """Improves eyelids skinning by defining their borders.\nSelect the vertices loop around the eyelids, then click this button. If the loop is not selected automatically (alt-click), the vertex selection order matters"""

    bl_idname = "arp.set_eyelids_borders"
    bl_label = "set_eyelids_borders"
    bl_options = {'UNDO'}

    action : StringProperty()

    @classmethod
    def poll(cls, context):
        return (context.active_object != None)

    def execute(self, context):
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        if self.action != "Clear":
            if context.active_object.type != 'MESH' or bpy.context.mode != "EDIT_MESH":
                self.report({'ERROR'}, "Select the vertices loop around the eyelids, warning: the selection order matters")
                return{'FINISHED'}
            else:
                head_obj = bpy.context.active_object
                mesh = bmesh.from_edit_mesh(head_obj.data)
                select_count = 0
                for v in mesh.verts:
                    if v.select:
                        select_count += 1
                    if select_count >= 4:
                        break
                if select_count < 4:
                    self.report({'ERROR'}, "At least 4 vertices must be selected")
                    return{'FINISHED'}

        try:
            _set_eyelids_borders(self)

        finally:
            context.preferences.edit.use_global_undo = use_global_undo
        return {'FINISHED'}


class ARP_OT_smart_pick_object(Operator):
    """Get the selected object"""

    bl_idname = "id.smart_pick_object"
    bl_label = "smart_pick_object"
    bl_options = {'UNDO'}

    op_prop : StringProperty(name = "Pick")

    @classmethod
    def poll(cls, context):
        return (context.active_object != None)

    def execute(self, context):
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        if context.active_object.type != 'MESH':
            self.report({'ERROR'}, "Select a mesh object")
            return{'FINISHED'}

        try:
            _pick_object(self.op_prop)

        finally:
            context.preferences.edit.use_global_undo = use_global_undo
        return {'FINISHED'}


handles_lines = [None]

# main drawing class
class ARP_OT_lines_fx(Operator):
    """Lines FX"""

    bl_idname = "id.lines_fx"
    bl_label = "lines_fx"

    active: BoolProperty()
    selected_bone = None

    def __init__(self):
        # color
        self.line_color = (0.0, 1.0, 0.0, 1.0)

        # internal vars
        self.shader = None
        self.batch = None
        self.region = None
        self.region_3d = None
        
        self.thigh_ref_name = ard.leg_ref_bones_dict['thigh']        
        self.leg_ref_name = ard.leg_ref_bones_dict['calf']
        self.arm_ref_name = ard.arm_ref_dict['arm']        
        self.forearm_ref_name = ard.arm_ref_dict['forearm']

    def draw(self, context):
        # Error handling
        # the selected bone must be an arm or leg bone
        if self.selected_bone == None:
            return
        # fixes the UnicodeDecodeError, the bone pointer may be lost when doing various operations
        try:
            self.selected_bone.name.startswith("test")
        except:
            return

        # get the selected bones chain
        leg_selected = False
        arm_selected = False

        if self.selected_bone.name.startswith(self.thigh_ref_name) or self.selected_bone.name.startswith(self.leg_ref_name):
            leg_selected = True
        elif self.selected_bone.name.startswith(self.arm_ref_name) or self.selected_bone.name.startswith(self.forearm_ref_name):
            arm_selected = True
        else:
            return

        point_a = None
        point_b = None

        # get side
        side = get_bone_side(self.selected_bone.name)

        rig_matrix = bpy.context.active_object.matrix_world.copy()
        
        if leg_selected:
            bone1 = get_edit_bone(self.thigh_ref_name + side)
            bone2 = get_edit_bone(self.leg_ref_name + side)
            bone1_head = rig_matrix @ bone1.head.copy()
            bone2_tail = rig_matrix @ bone2.tail.copy()
            center = (bone1_head + bone2_tail) / 2
            bone1_tail = rig_matrix @ bone1.tail.copy()
            point_a = bone1_tail

        elif arm_selected:
            bone1 = get_edit_bone(self.arm_ref_name + side)
            bone1_head = rig_matrix @ bone1.head.copy()   
            bone2 = get_edit_bone(self.forearm_ref_name + side)
            bone2_tail = rig_matrix @ bone2.tail.copy()
            center = (bone1_head + bone2_tail) / 2
            bone1_tail = rig_matrix @ bone1.tail.copy()
            point_a = bone1_tail

        
        # get arm plane normal
        plane_normal = (bone1_head - bone2_tail)
        prepole_dir = bone1_tail - center
        pole_pos = bone1_tail + (prepole_dir).normalized()
        pole_pos = project_point_onto_plane(pole_pos, bone1_tail, plane_normal)
        custom_dist = 1.0
        point_b = bone1_tail + ((pole_pos - bone1_tail).normalized() * (bone1_tail - bone1_head).magnitude * custom_dist)
        
        point_a_2d = bpy_extras.view3d_utils.location_3d_to_region_2d(self.region, self.region_3d, point_a, default=None)
        point_b_2d = bpy_extras.view3d_utils.location_3d_to_region_2d(self.region, self.region_3d, point_b, default=None)

        if point_a_2d == None or point_b_2d == None:
            return

        vertices = (point_a_2d, point_b_2d)

        # batch and shader
        # modes: POINTS, TRIS, TRI_FAN, LINES. Warning, LINES_ADJ does not work
        self.shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
        self.batch = batch_for_shader(self.shader, 'LINES', {"pos": vertices})

        # Render
        self.shader.bind()
        self.shader.uniform_float("color", self.line_color)
        bgl.glEnable(bgl.GL_BLEND)
        self.batch.draw(self.shader)
        bgl.glDisable(bgl.GL_BLEND)


    def modal(self, context, event):
        """
        # enable constant update for mouse-over evaluation function
        if context.area:
            context.area.tag_redraw()
        """
        obj = bpy.context.active_object
        end_modal = False
        # conditions to exit the operator
        # the active object must be an armature in edit mode, otherwise end the modal operator
        if obj == None:
            end_modal = True
        else:
            if obj.type != "ARMATURE":
                end_modal = True
            elif bpy.context.mode != "EDIT_ARMATURE":
                end_modal = True

        if end_modal or self.active == False or context.scene.arp_show_ik_chain_direction == False:
            if bpy.context.scene.arp_debug_mode:
                print('End Lines FX')
            try:
                bpy.types.SpaceView3D.draw_handler_remove(handles_lines[0], 'WINDOW')
            except:
                if bpy.context.scene.arp_debug_mode:
                    print('Handler already removed')
                pass
            if bpy.context.scene.arp_debug_mode:
                print("FINISHING MODAL")
            self.active = False
            context.scene.arp_show_ik_chain_direction = False
            
            return {'FINISHED'}

        # get the selected bone
        if len(get_selected_edit_bones()) > 0:
            self.selected_bone = get_selected_edit_bones()[0]

        return {'PASS_THROUGH'}


    def execute(self, context):
        args = (self, context)
        # first remove previous session handler if any
        try:
            bpy.types.SpaceView3D.draw_handler_remove(handles_lines[0], 'WINDOW')
            if bpy.context.scene.arp_debug_mode:
                print('Removed handler')
        except:
            if bpy.context.scene.arp_debug_mode:
                print('No handlers to remove')
            pass

        if self.active == True:
            if bpy.context.scene.arp_debug_mode:
                print('Start Lines FX')

            handles_lines[0] = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback_3_args, args, 'WINDOW', 'POST_PIXEL')
            context.window_manager.modal_handler_add(self)

            return {'RUNNING_MODAL'}

        return {'CANCELLED'}


    def draw_callback_3_args(self, op, context):
        self.region = context.region
        self.region_3d = context.space_data.region_3d
        self.draw(self)


class ARP_OT_report_message(Operator):
    """ Report a message in a popup window"""

    bl_label = 'Info'
    bl_idname = "arp.report_message"

    message : StringProperty(default="")
    icon_type : StringProperty(default='INFO')

    def draw(self, context):

        layout = self.layout
        split_message = self.message.split('\n')

        for i, line in enumerate(split_message):
            txt = line
            ic = None
            if '<icon>' in line:# myline blabla<icon>INFO
                line_split = line.split('<icon>')
                txt = line_split[0]
                ic = line_split[1]
                
            if i == 0:
                layout.label(text=txt, icon=self.icon_type)
            else:
                if ic:
                    layout.label(text=txt, icon=ic)
                else:
                    layout.label(text=txt)

    def execute(self, context):
        return {"FINISHED"}

    def invoke(self, context, event):
        wm = context.window_manager
        return wm.invoke_props_dialog(self, width=400)


class ARP_OT_mirror_shape_keys(Operator):
    """Create mirorred shape keys for opposite side, with drivers.\nShape keys names must end with side suffix such as .l or _L for left"""

    bl_idname = "arp.mirror_shape_keys"
    bl_label = "Mirror Shape Keys"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        obj = bpy.context.active_object
        if obj:
            if obj.type == "MESH":
                return True


    def execute(self, context):
        try:
            scn = bpy.context.scene
            # Disable subsurf for faster performances
            simplify_value = scn.render.use_simplify
            simplify_subd = scn.render.simplify_subdivision
            scn.render.use_simplify = True
            scn.render.simplify_subdivision = 0

            _mirror_shape_keys()

        finally:
            # Restore subsurf
            scn.render.use_simplify = simplify_value
            scn.render.simplify_subdivision = simplify_subd


        return {'FINISHED'}


class ARP_OT_add_corrective_bone(Operator):
    """Setup a corrective bone for the selected deforming bone(s) at the current rotation.\nIf 1 bone is selected, it must be an arm or leg bone.\nIf 2 bones are selected, can be any bones, the first bone selected must be the rotated bone"""

    bl_idname = "arp.add_corrective_bone"
    bl_label = "Add Corrective Bone"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        if is_object_arp(bpy.context.active_object):
            if bpy.context.mode == 'POSE':
                return True

    def execute(self, context):
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False
        scn = context.scene
        
        try:
            if len(get_selected_pose_bones()) == 1:
                # dedicated to arms and legs only
                _add_corrective_bone(self)
            elif len(get_selected_pose_bones()) == 2:
                # can be any bones
                _add_corrective_bone_universal(self)
            else:
                self.report({"ERROR"}, "One or two bones must be selected")

        finally:
            if scn.arp_corrective_shapes_data != '':
                angle = float(scn.arp_corrective_shapes_data.split(',')[2])
                
                if angle <= 4e-05:
                    self.report({'ERROR'}, 'Warning, 0 degree angle! The bones are probably not rotated yet.\nCancel, set the rotated pose, and recreate the driver')
                    
            context.preferences.edit.use_global_undo = use_global_undo

        return {'FINISHED'}


class ARP_OT_add_corrective_driver(Operator):
    """Add the driver for the selected shape key.\nThe mesh and the shape key must be selected"""

    bl_idname = "arp.add_corrective_driver"
    bl_label = "Add Corrective Driver"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        if bpy.context.active_object:
            if bpy.context.active_object.type == "MESH":
                return True

    def execute(self, context):
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False
        try:
            _add_corrective_driver(self)

            self.report({"INFO"}, "Driver Added!")

        finally:
            context.preferences.edit.use_global_undo = use_global_undo

        return {'FINISHED'}


class ARP_OT_cancel_corrective_driver(Operator):
    """Cancel the corrective driver creation"""

    bl_idname = "arp.cancel_corrective_driver"
    bl_label = "Cancel Corrective Driver"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        if is_object_arp(bpy.context.active_object):
            return True

    def execute(self, context):
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False
        try:
            _cancel_corrective_driver(self)

            self.report({"INFO"}, "Canceled")

        finally:
            context.preferences.edit.use_global_undo = use_global_undo

        return {'FINISHED'}


class ARP_OT_set_pose(Operator):
    """Pose the character into a predefined pose"""

    bl_idname = "arp.set_pose"
    bl_label = "Set Pose"
    bl_options = {'UNDO'}

    pose_type: EnumProperty(
        items=(('APOSE', 'A-Pose (UE4)', 'A-Pose, low arm angle similar to UE Mannequin'),
               ('APOSE_MANNY', 'A-Pose (UE5 Manny)', 'A-Pose, low arm angle similar to UE Manny skeleton'),
               ('TPOSE', 'T-Pose', 'T-Pose with horizontal arms')))
    exact_rotations: BoolProperty(default=True, description='Use exact rotations from the UE mannequin to define the pose.\
                    \nWill only work properly if the default Auto-Rig Pro bones axes were kept\nOterwise use the shortest rotation, preserving the bone roll (Y axis)')

    @classmethod
    def poll(cls, context):
        if is_object_arp(bpy.context.active_object):
            if bpy.context.mode == 'POSE' or bpy.context.mode == 'OBJECT':
                return True
                
    def invoke(self, context, event):
        wm = context.window_manager
        return wm.invoke_props_dialog(self, width=400)

    def draw(self, context):
        layout = self.layout
        layout.prop(self, 'exact_rotations', text='Exact Rotations')
        layout.prop(self, 'pose_type', text='')
        
        if 'rig_spine_count' in bpy.context.active_object.keys():
            if self.pose_type == 'APOSE_MANNY':
                if bpy.context.active_object['rig_spine_count'] != 6:
                    layout.label(text='Warning, UE5 Manny requires 6 spine bones', icon='ERROR')
                    layout.label(text='May give incorrect spine rotation since there is '+str(bpy.context.active_object['rig_spine_count'])+' spine bones')

    def execute(self, context):
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        # Disable subsurf for faster performances
        simplify_value = bpy.context.scene.render.use_simplify
        simplify_subd = bpy.context.scene.render.simplify_subdivision
        bpy.context.scene.render.use_simplify = True
        bpy.context.scene.render.simplify_subdivision = 0

        try:
            _set_pose(self)
            self.report({"INFO"}, "Pose Set!")

        finally:
            context.preferences.edit.use_global_undo = use_global_undo

            # Restore subsurf for faster performances
            bpy.context.scene.render.use_simplify = simplify_value
            bpy.context.scene.render.simplify_subdivision = simplify_subd

        return {'FINISHED'}

        
class ARP_OT_toggle_action_preserve_pose(Operator):
    """Enable or disable this action from export"""

    bl_idname = "arp.toggle_action_preserve_pose"
    bl_label = "toggle_action_preserve_pose"

    action_name : StringProperty(default="")

    def execute(self, context):     
        try:
            if self.action_name != "":
                act = bpy.data.actions.get(self.action_name)
                if act:
                    found_prop = False
                    if len(act.keys()):
                        if "arp_apply_pose" in act.keys():
                            act["arp_apply_pose"] = not act["arp_apply_pose"]                           
                            found_prop = True
                    if not found_prop:
                        act["arp_apply_pose"] = True

        finally:
            pass

        return {'FINISHED'}  
        
        
class ARP_OT_preserve_enable_all_actions(Operator):
    """Enable all actions to preserve when apply pose"""
    
    bl_idname = "arp.preserve_enable_all_actions"
    bl_label = ""
    
    def execute(self, context):        
        for act in bpy.data.actions:
            act['arp_apply_pose'] = True
      
        return {'FINISHED'}
        
        
class ARP_OT_preserve_disable_all_actions(Operator):
    """Disable all actions to preserve when apply pose"""
    
    bl_idname = "arp.preserve_disable_all_actions"
    bl_label = ""
    
    def execute(self, context):        
        for act in bpy.data.actions:
            act['arp_apply_pose'] = False
      
        return {'FINISHED'}
        

class ARP_OT_apply_pose_as_rest(Operator):
    """Apply the current pose as rest pose: apply armature modifiers and align reference bones with controller bones (like an inverted 'Match to Rig')\nClick Match to Rig afterward to complete """
    bl_label = 'Apply pose as rest'
    bl_idname = "arp.apply_pose_as_rest"

    skinned_meshes = []    
    rig = None
    rig_add = None
    instanced_meshes = []
    objects_hidden = []
    collections_hidden = []
    simplify_value = None
    simplify_subd = None
    preserve_anim : BoolProperty(default=False, name="Preserve Anim", description="Bake actions based on the new rest pose, to preserve animations")
    key_all_frames: BoolProperty(default=True, name="Key All Frames", description="One keyframe per frame if enabled for best preservation, otherwise only key current keyframes")
    actions_list = []
    
    @classmethod
    def poll(cls, context):
        if is_object_arp(bpy.context.active_object):
            if bpy.context.mode == 'POSE' or bpy.context.mode == 'OBJECT':
                return True
                
                
    def init_functions(self):
        self.objects_hidden = []
        self.collections_hidden = []
        self.simplify_value = None
        self.simplify_subd = None
        
        # show all collections
        for col in bpy.data.collections:           
            if col.hide_viewport:
                col.hide_viewport = False
                self.collections_hidden.append(col.name)             

        # show all meshes
        for obj in bpy.data.objects:
            if len(obj.modifiers) == 0 or obj.type != "MESH":
                continue
            if obj.hide_viewport:
                obj.hide_viewport = False
                self.objects_hidden.append(obj.name)

        # Disable subsurf for faster performances
        self.simplify_value = bpy.context.scene.render.use_simplify
        self.simplify_subd = bpy.context.scene.render.simplify_subdivision
        bpy.context.scene.render.use_simplify = True
        bpy.context.scene.render.simplify_subdivision = 0
        
        
    def end_functions(self):
        # restore hidden items        
        for obj_name in self.objects_hidden:
            obj = get_object(obj_name)
            obj.hide_viewport = True

        for col_name in self.collections_hidden:          
            col = bpy.data.collections.get(col_name)           
            col.hide_viewport = True

        # Restore subsurf for faster performances
        bpy.context.scene.render.use_simplify = self.simplify_value
        bpy.context.scene.render.simplify_subdivision = self.simplify_subd
        
                
    def invoke(self, context, event):
        # init props
        self.skinned_meshes = []
        self.rig = bpy.data.objects.get(bpy.context.active_object.name)
        self.rig_add = get_rig_add(self.rig)
        self.instanced_meshes = []
    
        try:
            self.init_functions()
            
            # look for instanced meshes, not compliant when applying modifiers
            for obj in bpy.data.objects:
                if len(obj.modifiers) == 0 or obj.type != "MESH" or is_object_hidden(obj):
                    continue
                    
                for modindex, mod in enumerate(obj.modifiers):
                    if mod.type != "ARMATURE":
                        continue
                    if (mod.object != self.rig and mod.object != self.rig_add) or mod.object == None:
                        continue       
                
                if obj.data.users > 1:
                    self.instanced_meshes.append(obj.name)
                    continue
                    
                self.skinned_meshes.append(obj)
                
        finally:
            self.end_functions()
            
        # actions
        if len(bpy.data.actions):
            self.actions_list = []
        
            for act in bpy.data.actions:
                if not act.name in self.actions_list:
                    self.actions_list.append(act.name)
                
        # open dialog
        if len(self.instanced_meshes) or len(bpy.data.actions):          
            wm = context.window_manager
            return wm.invoke_props_dialog(self, width=400)

        self.execute(context)

        return {'PASS_THROUGH'}
        
        
    def draw(self, context):
        layout = self.layout
        if len(self.instanced_meshes):
            layout.label(text='Warning, some meshes are instanced (multiple users):', icon='ERROR')
            
            for n in self.instanced_meshes:
                layout.label(text='- '+n)            
            
            layout.label(text="Applying the rest pose on these objects won't work properly.")
            layout.label(text="Please make them single user first.")
            layout.label(text="Continue anyway?")  

        if len(bpy.data.actions):
            layout.separator()
            layout.prop(self, 'preserve_anim')            
            if self.preserve_anim:
                layout.prop(self, 'key_all_frames')
                row = layout.row(align=True)             
                row.operator('arp.preserve_enable_all_actions', text='Enable All')
                row.operator('arp.preserve_disable_all_actions', text='Disable All')
        
                def show_action_row(_col, _act_name):
                    row2 = _col.row(align=True)
                    row2.label(text=_act_name)
                    icon_name = 'CHECKBOX_DEHLT'#'CHECKBOX_HLT'
                    act = bpy.data.actions.get(_act_name)
                    if len(act.keys()):
                        if "arp_apply_pose" in act.keys():
                            if act["arp_apply_pose"] == True:
                                icon_name = 'CHECKBOX_HLT'
                    op1 = row2.operator('arp.toggle_action_preserve_pose', text='', icon=icon_name)
                    op1.action_name = _act_name
                    op = row2.operator('arp.delete_action', text='', icon = 'X')
                    op.action_name = _act_name
        
                for actname in self.actions_list:                 
                    col = layout.column(align=True)                              
                    show_action_row(col, actname)        
        

    def execute(self, context):
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False
        
        self.objects_hidden = []
        self.collections_hidden = []
        
        try:            
            self.init_functions()
            
            if self.preserve_anim:
                # copy armature
                bpy.ops.object.mode_set(mode='OBJECT')
                bpy.ops.object.select_all(action='DESELECT')
                set_active_object(self.rig.name)
                
                acts = [act.name for act in bpy.data.actions]
                duplicate_object(new_name=self.rig.name+"_POSECOPY")
                # remove new temp action created when duplicating
                for act in bpy.data.actions:
                    if not act.name in acts:
                        bpy.data.actions.remove(act)
                
                bpy.ops.object.mode_set(mode='OBJECT')
                bpy.ops.object.select_all(action='DESELECT')
                set_active_object(self.rig.name)
                
                
            # execute
            _apply_pose_as_rest(self)            
            
            if self.preserve_anim:
                _apply_pose_preserve_anim(self)                

            self.report({"INFO"}, "Pose applied!")

        finally:        
            self.end_functions()            

            context.preferences.edit.use_global_undo = use_global_undo

        return {'FINISHED'}


class ARP_OT_set_character_name(Operator):
    """Set the character name in collections and objects names"""

    bl_idname = "arp.set_character_name"
    bl_label = "Set Character Name"
    bl_options = {'UNDO'}

    name: StringProperty(default="character")

    @classmethod
    def poll(cls, context):
        return is_object_arp(bpy.context.active_object)

    def draw(self, context):
        layout = self.layout
        layout.prop(self, "name", text="")

    def execute(self, context):
        _set_character_name(self)
        
        return {'FINISHED'}
        

    def invoke(self, context, event):
        # Open dialog
        wm = context.window_manager
        return wm.invoke_props_dialog(self)



class ARP_OT_show_limb_params(Operator):
    """Show the selected limb parameters"""

    bl_idname = "arp.show_limb_params"
    bl_label = "Limb Options"
    bl_options = {'UNDO'}
    
    def update_limb_name(self, context):
        bone_names = ard.fingers_control + ard.fingers_control_ik + ard.arm_control + ard.leg_control + ard.head_control + ard.facial_control + ard.ear_control + ard.neck_control + ard.spine_control
        name = ''
        if self.limb_type == "spline_ik":
            name = self.spline_name
        elif self.limb_type == "bbones":
            name = self.bbones_name
        
        if name == '':
            return
            
        valid = True
        for bone_name in bone_names:
            if bone_name.startswith(name) or bone_name.startswith('c_'+name):
                valid = False
                break
        
        if valid:
            print("Name valid:", name)
        else:
            if self.limb_type == "spline_ik":
                pref = 'sik_'
            elif self.limb_type == 'bbones':
                pref = 'bb_'
                
            new_name = pref+name
            
            print("Warning, name conflict, auto renaming:", new_name)     
            
            if self.limb_type == "spline_ik":            
                self.spline_name = new_name
            elif self.limb_type == "bbones":
                self.bbones_name = new_name   
                
    
    limb_type: StringProperty(default='')

    # ears
    ear_count: IntProperty(default=2, min=1, max=16, description="Set the number of ear bones")

    # neck
    neck_count: IntProperty(default=1, min=1, max=16, description="Set the number of neck bones")
    neck_twist: BoolProperty(default=False, description="Add neck twist bones")
    neck_bendy: IntProperty(default=1, min=1, max=256, description="Use bendy bones soft deformations for the neck bone if greater than 1")

    # facial
    skull_bones: BoolProperty(default=False, description="Add 3 skull bones to deform the mouth, eyes and head top areas")
    facial: BoolProperty(default=True, description="Facial controllers (mouth, eyes, eyelids...)")
    eye_target_dist: FloatProperty(default=1.0, description="Eye target controllers distance from the head")
    
    enable_eyelids_tweak: BoolProperty(default=False, description="Controller to adjust the upper and lower eyelid surface curvature")
    eyelids_amount: IntProperty(min=1, max=32, description="Number of eyelid bones per upper/lower eyelids\nE.g 3 = 3 upper eyelids, 3 lower eyelids", default=3)
    eyelids_updt_transf: BoolProperty(default=False, description='Force update of existing eyelids transforms, grid alignment.\nNew bones added will always be grid-aligned')
    eyelid_align_rot: BoolProperty(default=True, description="Align the c_eyelid bones rotation when Match to Rig")
    eyelid_speed_fac: FloatProperty(default=1.0, description="Factor to adjust the eyelid rotation speed when moving the eyelid controller")
    
    skulls_align: BoolProperty(default=True, description="Align the c_skull bones when Match to Rig")
    
    lips_amount: IntProperty(default=2, name='Lips Amount', description='Number of lips bones per quarter side, excluding middle and corner bones', min=1, max=32)
    lips_masters: IntProperty(default=1, name='Lips Masters Frequency', description='Add a lip master bone every Nth bone.\nIf set to 1: no masters, minimum is 2', min=1, max=32)
    lips_masters_linear: FloatProperty(default=0.0, name='Lips Masters Linear', description='0 = curved interpolation when dragging lips bones with masters, 1 = linear', min=0.0, max=1.0)
    lips_roll_cns: BoolProperty(default=False, name='Lips Roll Constraints', description='Add lips roll constraints, so that lips automatically rotate when moving roll controllers\nDisabling it implies to use shape keys instead, or no need for lips roll at all')
    lips_roll_speed: FloatProperty(default=1.0, name='Lips Roll Speed', description='Factor to adjust the speed of the roll motion when moving the controller', min=0.0, max=10000.0)
    jaw_speed: FloatProperty(default=1.0, name='Jaw Speed Fac', description='Factor to adjust the jaw rotation speed when moving the jaw controller')
    lips_soft_lin_corner: FloatProperty(default=0.0, description='0 = curved interpolation, 1 = linear interpolation, applied to other lip bones when moving the lips corners', min=0.0, max=1.0)
    lips_soft_lin_jaw: FloatProperty(default=0.0, description='0 = curved interpolation, 1 = linear interpolation, applied to other lip bones when moving the jaw', min=0.0, max=1.0)
    lips_soft_limit_corner: IntProperty(default=0, min=0, max=32, description='Limit the Soft Lips effect to a specified range near the lips corners (Corners).\nE.g: If set to 4, only the 4 lips bones near the corners will deform softly.\n0 = no limits')
    lips_soft_limit_jaw: IntProperty(default=0, min=0, max=32, description='Limit the Soft Lips effect to a specified range near the lips corners (Jaw).\nE.g: If set to 4, only the 4 lips bones near the corners will deform softly.\n0 = no limits')
    lips_updt_transf: BoolProperty(default=False, name='Update Lips Transforms', description='Force update of existing lips transforms, grid alignment.\nNew bones added will always be grid-aligned')
    auto_lips: BoolProperty(default=True, description="The lips controllers will move automatically with the jaw and lips corners controllers")    
    auto_lips_visual: BoolProperty(default=False, description="The Soft Lips effect will be only visual, it won't deform (except the lips corner)\nUseful when using shape keys.")
    lips_floor: BoolProperty(default=False, description="The upper lips will collide with the lower lips when the jaw is raising, if enabled")
    #lips_floor_full: BoolProperty(default=False, description="The bottom lips collide 100% with the upper lips instead of 50%, but may lead the chin to raise too fast compared to the lips")
    lips_floor_offset: FloatProperty(default=0.0, description="Offset distance at which the lower lips will collide with the upper lips")
    lips_corner_offset: BoolProperty(default=False, description="Add an offset controller for the lips corners")
    eyebrows_type: EnumProperty(items=(('type_1', 'type_1', 'Default eyebrows'),
                                        ('type_2', 'type_2', 'Automatic tracking of eyebrow_01 toward eyebrow_01_end'),
                                        ('type_3', 'type_3','Offset controllers to separate the eyebrow mesh from the skin')),
                                        description="Eyebrows rig type")
    lips_offset: BoolProperty(default=False, description="Add a lips offset controller to shift all lips bones at once")
    lips_big_masters: BoolProperty(default=False, description="Add lips master controllers for up and down lips")
    jaw_rotation: BoolProperty(default=False, description="Use rotation instead of location to open the jaw")
    jaw_separate_location: BoolProperty(default=False, description="Separate the jaw location (the pivot point can be moved when moving the jaw controller)")
    facial_mouth: BoolProperty(default=True, description="Enable the mouth controllers")   
    facial_teeth: BoolProperty(default=True, description="Enable the teeth controllers")
    facial_tongue: BoolProperty(default=True, description="Enable the tongue controllers")
    facial_chins: BoolProperty(default=True, description="Enable the chin controllers")
    facial_noses: BoolProperty(default=True, description="Enable the nose controllers")
    facial_eye_l: BoolProperty(default=True, description="Enable the left eye controllers")
    facial_eye_r: BoolProperty(default=True, description="Enable the right eye controllers")
    facial_eyebrow_l: BoolProperty(default=True, description="Enable the left eyebrow controllers")
    facial_eyebrow_r: BoolProperty(default=True, description="Enable the right eyebrow controllers")
    facial_cheeks: BoolProperty(default=True, description="Enable cheek bones")
    
    
    # arms
    finger_thumb: BoolProperty(default=True)
    finger_index: BoolProperty(default=True)
    finger_middle: BoolProperty(default=True)
    finger_ring: BoolProperty(default=True)
    finger_pinky: BoolProperty(default=True)
    finger_pinky_independent: BoolProperty(default=False)
    fingers_ik: BoolProperty(default=False, description="Add IK controls for fingers, with IK-FK switch and snap settings")
    fingers_ik_shape: EnumProperty(items=(('cs_cube_solid', 'Solid Box', 'cs_cube_solid', 'MESH_CUBE', 1), ('cs_sphere_solid', 'Solid Sphere', 'cs_sphere_solid', 'MESH_UVSPHERE', 2), ('cs_box', 'Box', 'cs_box', 'MESH_CUBE', 3), ('cs_sphere', 'Sphere', 'cs_sphere', 'MESH_CIRCLE', 4)), description="Fingers IK target (tip) default shape")
    fingers_ik_color: FloatVectorProperty(name="Color", subtype="COLOR_GAMMA", default=(0.8, 0.432, 0.0), min=0.0, max=1.0, description="Color of IK controllers (tip)")
    fingers_ik2_shape: EnumProperty(items=(('cs_cube_solid', 'Solid Box', 'cs_cube_solid', 'MESH_CUBE', 1), ('cs_sphere_solid', 'Solid Sphere', 'cs_sphere_solid', 'MESH_UVSPHERE', 2), ('cs_box', 'Box', 'cs_box', 'MESH_CUBE', 3), ('cs_sphere', 'Sphere', 'cs_sphere', 'MESH_CIRCLE', 4)), description="Fingers IK target (root) default shape")
    fingers_ik2_color: FloatVectorProperty(name="Color", subtype="COLOR_GAMMA", default=(0.8, 0.432, 0.0), min=0.0, max=1.0, description="Color of IK (root) controllers")

    fingers_ik_parent: EnumProperty(items=(('hand', 'Hand', 'hand'), ('metacarp', 'Metacarp', 'metacarp')), description="IK target parent bone.\nCan also be user-defined by adding more ChildOf constraints")
    fingers_ik_pole_parent: EnumProperty(items=(('hand', 'Hand', 'hand'), ('metacarp', 'Metacarp', 'metacarp')), description="IK pole parent bone")
    fingers_ik_pole_shape: EnumProperty(items=(('cs_arrow', 'Arrow', 'cs_arrow', 'EMPTY_SINGLE_ARROW', 1), ('cs_sphere', 'Sphere', 'cs_sphere', 'MESH_CIRCLE', 2)), description="Fingers IK pole default shape")
    fingers_ik_pole_color: FloatVectorProperty(name="Color", subtype="COLOR_GAMMA", default=(1.0, 0.9, 0.9), min=0.0, max=1.0, description="Color of IK pole controllers")
    fingers_ik_pole_distance: FloatProperty(default=1.0, description="IK pole distance from fingers")
    arm_ikpole_distance: FloatProperty(default=1.0, description="IK Pole distance from the elbow")
    arm_twist_bones: IntProperty(default=1, min=1, max=32, description="Number of twist bones per bone (arm, forearm).\nDisabled if secondary controllers are Bendy Bones")
    arm_bbones_ease_out: BoolProperty(default=True, description="The Ease Out property of the bendy-bones is driven by secondary controllers if true")
    arm_fk_lock: BoolProperty(default=False, description="Add an Arm Lock setting in FK mode to switch the arm parent space")
    arm_ikfk_default: EnumProperty(items=(('DEFAULT', 'Default Preferences', 'Default as set in the addon preferences'), ('IK', 'IK', 'IK'), ('FK', 'FK', 'FK')), description='Arm IK-FK default switch value', name="IK-FK Default")
    arm_softik: BoolProperty(default=False, name="Soft IK", description="Enables Soft IK chains for smoother results, to avoid typical IK pop effect\nWarning, leads to slight stretch even when there is no stretch applied")
    arm_auto_ik_roll: BoolProperty(default=True, name="Auto IK Roll", description="Automatically align IK bones axes for coherent rotation axes and perfectly lined up IK pole")
    
    # arms wings
    arm_wings: BoolProperty(default=False, description="Enable feather bones")
    arm_feathers: IntProperty(default=1, min=1, max=32, description="Number of feathers along the arm bone")
    forearm_feathers: IntProperty(default=1, min=1, max=32, description="Number of feathers along the forearm bone")
    hand_feathers: IntProperty(default=1, min=1, max=32, description="Number of feathers along the hand bone")
    hand_ik_offset: BoolProperty(default=False, description="Add an additional IK offset controller")

    feathers_layers: IntProperty(default=1, min=1, max=32, description="Number of bones layer, per feather, in case of multiple feathers on top of each other")
    feathers_subdiv: IntProperty(default=1, min=1, max=32, description="Number of bones per feather in order to curve the feather")
    feathers_update_transforms: BoolProperty(default=True, description="Update existing reference feather bones transforms when clicking the OK button (grid align).\nIf disabled, existing feathers won't move. Useful to add new feathers while preserving existing ones.")
    feathers_parent_layers: BoolProperty(default=True, description="Parent feathers layers. If disabled, feather layers move independently.")
    feathers_fold_controller: BoolProperty(default=False, description="Add a controller to fold the arms and feathers by scaling it.\nRequires an action containing 'rig_wings_fold' in its name, rest pose at frame 0, folded pose at frame 10")

    # legs
    three_bones_leg: BoolProperty(default=False, description="3 bones leg instead of 2, one bone is added at the root.\n2 or 3 bones can be used in the IK chain,  using the '3 Bones IK' parameter")
    toes_thumb: BoolProperty(default=True)
    toes_index: BoolProperty(default=True)
    toes_middle: BoolProperty(default=True)
    toes_ring: BoolProperty(default=True)
    toes_pinky: BoolProperty(default=True)
    toes_pivot: BoolProperty(default=False, description="Add a controller to rotate the IK foot from the toes pivot point")
    foot_ik_offset: BoolProperty(default=False, description="Add an IK offset controller, offering another layer of control")
    leg_ikpole_distance: FloatProperty(default=1.0, description="IK Pole distance from the knee")
    leg_twist_bones: IntProperty(default=1, min=1, max=32, description="Number of twist bones per bone (thigh, leg).\nDisabled if secondary controllers are Bendy Bones")
    leg_bbones_ease_out: BoolProperty(default=True, description="The Ease Out property of the bendy-bones is driven by secondary controllers if true")
    leg_foot_roll_distance: FloatProperty(default=1.0, description='Distance of the "c_foot_roll_cursor" controller from the foot, to adjust its visual position')
    leg_foot_roll_fac: FloatProperty(default=1.0, description="Speed/factor for the foot_roll_cursor motion")
    leg_ikfk_default: EnumProperty(items=(('DEFAULT', 'Default', 'Default as set in the addon preferences'), ('IK', 'IK', 'IK'), ('FK', 'FK', 'FK')), description='LEG IK-FK default switch value', name="IK-FK Default")
    leg_softik: BoolProperty(default=False, name="Soft IK", description="Enables Soft IK chains for smoother results, to avoid typical IK pop effect\nWarning, leads to slight stretch even when there is no stretch applied")
    leg_auto_ik_roll: BoolProperty(default=True, name="Auto IK Roll", description="Automatically align IK bones axes for coherent rotation axes and perfectly lined up IK pole")
    
    # spine
    bottom: BoolProperty(default=False, description="Add bottom controllers")
    align_root_master: BoolProperty(default=True, description="Align the c_root_master bone when Match to Rig, otherwise let it be for manual adjustment")
    spine_master: BoolProperty(default=False, description='Add a spine master controller to rotate and move all spine bones at once')
    spine_master_space: EnumProperty(items=(('LOCAL', 'Local', 'Local space is used for the spine master constraints.\nThe rotation originates from the pelvis '), 
                                            ('CUSTOM', 'Custom', 'Custom Space is used for the spine master constraint\nThe rotation originates from the chest')),
                                            name='Spine Master Constraints Space',
                                            description='Space setting used by the spine master constraints')
    spine_master_stretchy: BoolProperty(default=False, description='Add automatic stretch and squash when moving the spine master')
    
    # spline IK
    spline_type: EnumProperty(items=(
        ('1', 'Simple', '1 spline controller per bone, plus a "Curvy" controller'),
        ('2', 'Advanced', 'Arbitrary number of spline controller per bone, and independent tweak controllers')),
        description="Type of the IK Spline limb", name="IK Spline Type")
    spline_count: IntProperty(default=4, min=2, max=1024, description="Number of bones")
    spline_cont_freq: IntProperty(default=2, min=1, max=1024, description="Add a spline master controller every Nth bone")
    spline_interpolation: EnumProperty(items=(
        ('SMOOTH', 'Smooth', 'Curvy, soft interpolation'),
        ('LINEAR', 'Linear', 'Straight, angular interpolation')),
        description="Type of weight interpolation for spline vertices in-between master controllers", name="Interpolation Type", default='SMOOTH')
    spline_bendy: IntProperty(default=0, min=0, max=1024, description="Number of bendy bones per bone for a smoother result.\nNote: Bendy-Bones are not export compliant, keep it to 0 for export")
    spline_ik_multiple_count: IntProperty(default=3, min=2, max=128, description="Bone range")# Todo, multiple masters
    spline_smoothness: IntProperty(min=2, max=6, default=4, description="Curve smoothness. Decrease this value if the curve shape is too smooth")
    spline_parent_master: EnumProperty(items=(
        ('stretch', 'stretch', ''),
        ('none', 'None', '')),
        description="Parent of the master controllers of the chain", name="Parent Master", default="stretch")
    spline_parent_last: EnumProperty(items=(
        ('c_spline_tip', 'c_spline_tip', ''),
        ('c_spline_root', 'c_spline_root', ''),
        ('none', 'None', '')),
        description="Parent of the last controller of the chain", name="Parent Last")
    spline_parent_last_master: EnumProperty(items=(
        ('c_spline_root', 'c_spline_root', ''),
        ('none', 'None', '')),
        description="Parent of c_spline_tip controller of the chain", name="Parent Master Last", default="c_spline_root")
    spline_side: EnumProperty(name="Side", items=(
        ('.x', 'Middle (.x)', ''),
        ('.l', 'Left (.l)', ''),
        ('.r', 'Right (.r)', '')),
        description="Side of the spline IK limb: left, right or middle")
    spline_name: StringProperty(default="spline", description="Spline bones names", update=update_limb_name)
    spline_deform: BoolProperty(default=True, description="Enable or disable skinning influence. Disabling may be useful when creating manually extra controllers on top of it")
    spline_update_transforms: BoolProperty(default=True, description="Update reference bones transforms when clicking the OK button.\nIf the bone count has changed, transforms are always updated")

    # bendy bones
    bbones_count: IntProperty(default=4, min=1, max=1024, description="Number of bendy-bones")
    bbones_segments: IntProperty(default=5, min=1, max=1024, description="Number of bendy-bones segments per bone")
    bbones_side: EnumProperty(name="Side", items=(
        ('.x', 'Middle (.x)', ''),
        ('.l', 'Left (.l)', ''),
        ('.r', 'Right (.r)', '')),
        description="Side of the bendy-bones limb: left, right or middle")
    bbones_name: StringProperty(default="bbones", description="Bendy bones name", update=update_limb_name)
    bbones_scale: FloatProperty(default=1.0, description="Size of the controller shapes")

    # tail
    tail_master_at_root: BoolProperty(name="Master Controller at Root", description="Position the tail master controller at the root (first bone)", default=True)
    tail_count: IntProperty(name="Tail Count", description='Number of tail bones', default=4, min=1, max=32)
    tail_side: EnumProperty(name="Side", items=(
        ('.x', 'Middle (.x)', ''),
        ('.l', 'Left (.l)', ''),
        ('.r', 'Right (.r)', '')),
        description="Side of the tail limb: left, right or middle")
    tail_update_transforms: BoolProperty(name="Update Transforms", description='Update bones transforms (aligned linearly). Enabled automatically if the tail count changed')
    
    side: StringProperty(default="")
    reset_to_default_settings: BoolProperty(default=True, description="Parameter to skip reset to default settings, useful when setting limbs from operators or other means")


    def __init__(self):
        if self.reset_to_default_settings:
            #print("RESET TO DEFAULTS")
            self.ear_count_default = 2
            self.neck_count_default = 1
            self.neck_twist_default = False
            self.neck_bendy_default = 1
            self.eye_target_dist_default = 1.0
            self.enable_eyelids_tweak = False
            self.eyelids_amount = 3
            self.eyelids_updt_transf = False
            self.eyelid_align_rot_default = True
            self.eyelid_speed_fac = 1.0
            self.skulls_align = True
            self.lips_amount = 2  
            self.lips_masters = 1    
            self.lips_masters_linear = 0.0
            self.lips_roll_cns = False
            self.lips_roll_speed = 1.0
            self.jaw_speed = 1.0
            self.lips_soft_lin_corner = 0.0
            self.lips_soft_lin_jaw = 0.0
            self.lips_updt_transf = False
            self.auto_lips = True
            self.lips_soft_limit_corner = 0
            self.lips_soft_limit_jaw = 0
            self.auto_lips_visual = False
            self.lips_floor = False
            #self.lips_floor_full = False
            self.lips_floor_offset = 0.0
            self.lips_corner_offset = False
            self.eyebrows_type ='type_1'
            self.lips_offset = False
            self.lips_big_masters = False
            self.jaw_rotation = False
            self.jaw_separate_location = False
            self.facial_mouth = True       
            self.facial_teeth = True
            self.facial_tongue = True
            self.facial_chins = True
            self.facial_noses = True
            self.facial_eye_l = True
            self.facial_eye_r = True
            self.facial_eyebrow_l = True
            self.facial_eyebrow_r = True
            self.facial_cheeks = True
            self.finger_thumb = True
            self.finger_index = True
            self.finger_middle = True
            self.finger_ring = True
            self.finger_pinky = True
            self.finger_pinky_independent = False
            self.fingers_ik = False
            self.fingers_ik_shape = 'cs_cube_solid'
            self.fingers_ik_color = (0.8, 0.432, 0.0)
            self.fingers_ik2_shape = 'cs_cube_solid'
            self.fingers_ik2_color = (0.8, 0.432, 0.0)
            self.fingers_ik_parent = 'hand'
            self.fingers_ik_pole_parent = 'hand'
            self.fingers_ik_pole_shape = 'cs_arrow'
            self.fingers_ik_pole_color = (1.0, 0.9, 0.9)
            self.fingers_ik_pole_distance =1.0
            self.arm_ikpole_distance = 1.0
            self.arm_twist_bones = 1
            self.arm_bbones_ease_out = True
            self.arm_ikfk_default = 'DEFAULT'
            self.arm_wings = False
            self.arm_feathers = 1
            self.forearm_feathers =1
            self.hand_feathers = 1
            self.hand_ik_offset = False
            self.arm_softik = False
            self.arm_auto_ik_roll = True
            self.feathers_layers =1
            self.feathers_subdiv =1
            self.feathers_update_transforms = True
            self.feathers_parent_layers = True
            self.feathers_fold_controller = False
            self.three_bones_leg = False
            self.leg_softik = False
            self.leg_auto_ik_roll = True
            self.toes_thumb = True
            self.toes_index = True
            self.toes_middle = True
            self.toes_ring = True
            self.toes_pinky = True
            self.toes_pivot = False
            self.foot_ik_offset = False
            self.leg_ikpole_distance =1.0
            self.leg_twist_bones = 1
            self.leg_bbones_ease_out = True
            self.leg_foot_roll_distance =1.0
            self.leg_foot_roll_fac = 1.0
            self.leg_ikfk_default = 'DEFAULT'
            self.bottom = False
            self.align_root_master = True
            self.spine_master = False
            self.spine_master_space = 'LOCAL'
            self.spine_master_stretchy = False
            self.spline_type = '1'
            self.spline_count = 4
            self.spline_cont_freq = 2
            self.spline_interpolation ='SMOOTH'
            self.spline_bendy = 0
            self.spline_ik_multiple_count = 3
            self.spline_smoothness = 4
            self.spline_parent_master = "stretch"
            self.spline_parent_last = 'c_spline_tip'
            self.spline_parent_last_master = "c_spline_root"
            self.spline_side = '.x'
            self.spline_name = "spline"
            self.spline_deform = True
            self.spline_update_transforms = True
            self.bbones_count = 4
            self.bbones_segments = 5
            self.bbones_side = '.x'
            self.bbones_name = "bbones"
            self.bbones_scale = 1.0
            self.tail_master_at_root = True
            self.tail_count = 4
            self.tail_side = '.x'
            self.tail_update_transforms = True
            
        else:
            self.reset_to_default_settings = True


    @classmethod
    def poll(cls, context):
        if is_object_arp(bpy.context.active_object):
            if bpy.context.mode == 'EDIT_ARMATURE':
                if len(context.selected_editable_bones) > 0:
                    return True


    def draw(self, context):
        layout = self.layout
        layout.label(text=self.limb_type.title())

        rig = context.active_object
        scn = context.scene

        if self.limb_type == "spine":
            layout.prop(rig, "rig_spine_count", text="Count")
            layout.prop(self, "spine_master", text="Spine Master Controller")
            col = layout.column()
            col.prop(self, "spine_master_space", text="Space")
            col.prop(self, "spine_master_stretchy", text="Stretch and Squash")
            col.enabled = self.spine_master
            layout.separator()
            layout.prop(self, "bottom", text="Bottom")
            layout.prop(self, "align_root_master", text="Align Root Master")
            layout.separator()
        elif self.limb_type == "tail":
            layout.prop(self, "tail_count", text="Count")
            layout.prop(self, "tail_master_at_root")
            layout.prop(self, 'tail_side', text="Side")
            layout.prop(self, 'tail_update_transforms', text="Update Transforms")
            layout.separator()
        elif self.limb_type == "neck":
            layout.prop(self, "neck_count", text="Count")
            col = layout.column()
            col.enabled = self.neck_count > 1
            col.prop(self, "neck_twist", text="Twist Bones")
            col = layout.column()
            col.prop(self, "neck_bendy", text="Bendy Bones")
            layout.separator()
        elif self.limb_type == "head":
            layout.prop(self, "skull_bones", text="Skulls")
            col = layout.column()
            col.prop(self, "skulls_align", text="Align Skulls")
            col.enabled = self.skull_bones
            layout.prop(self, "facial", text="Facial", toggle=True)
            
            col_f = layout.column()
            col_f.enabled = self.facial
            row = col_f.row()
            row.prop(self, 'facial_eyebrow_r', text='Eyebrow Right')
            row.prop(self, 'facial_eyebrow_l', text='Eyebrow Left')            
            row = col_f.row()
            row.prop(self, 'facial_eye_r', text='Eye Right')
            row.prop(self, 'facial_eye_l', text='Eye Left')
            
            col_eyel = col_f.column()
            col_eyel.enabled = self.facial_eye_r or self.facial_eye_l
            col_eyel.prop(self, "enable_eyelids_tweak", text='Eyelids Tweak Controllers')
            row = col_eyel.row()
            row.prop(self, "eyelids_amount", text='Eyelids Amount')
            row.prop(self, 'eyelids_updt_transf', text='Update Transforms')
            col_eyel.prop(self, "eye_target_dist", text="Eye Targets Distance")
            col_eyel.prop(self, "eyelid_align_rot", text="Align Eyelids")
            col_eyel.prop(self, "eyelid_speed_fac", text="Eyelid Speed Fac")
            
            col_f = col_f.column()
            col_f.separator()
            col_f.prop(self, 'facial_noses', text='Nose')
            col_f.prop(self, 'facial_cheeks', text='Cheeks')
            
            col_f.separator()
            col_f.prop(self, 'facial_mouth', text='Mouth')            
            col_m = col_f.column()            
            col_m.enabled = self.facial_mouth 
            
            ro = col_m.row()
            ro.prop(self, 'lips_roll_cns', text='Lips Roll Constraints')
            roo = ro.row()
            roo.enabled = self.lips_roll_cns
            roo.prop(self, 'lips_roll_speed', text='Lips Roll Speed')
            
            row1 = col_m.row()
            if scn.arp_retro_lips:
                row1.enabled = False
                col_m.label(text='Advanced settings are locked: Legacy Soft Lips is enabled, ')
            row1.prop(self, 'lips_amount', text='Lips Amount')
            row1.prop(self, 'lips_updt_transf', text='Update Transforms')     
            
            row1 = col_m.row()
            row1.prop(self, 'lips_masters', text='Lips Masters Freq')
            row1.prop(self, 'lips_masters_linear', text='Linear (Masters)', slider=True)
             
            col_m.prop(self, "auto_lips", text="Soft Lips")            
            col_m_al = col_m.column()
          
            row = col_m_al.row()
            row.prop(self, 'lips_soft_lin_corner', text='Linear (Corners)', slider=True)
            row.prop(self, 'lips_soft_limit_corner', text='Limit (Corners)')
            if scn.arp_retro_lips:
                row.enabled = False
            row = col_m_al.row()
            row.prop(self, 'lips_soft_lin_jaw', text='Linear (Jaw)', slider=True)
            row.prop(self, 'lips_soft_limit_jaw', text='Limit (Jaw)')
            if scn.arp_retro_lips:
                row.enabled = False
                
            col_m_al.prop(self, "auto_lips_visual", text="Soft Lips: Visual Only")
            col_m_al.enabled = self.auto_lips
            col_m_al.prop(self, "lips_floor", text="Sticky Lips")
            col_sl = col_m.column()
            col_sl.enabled = self.lips_floor
            col_sl.prop(self, "lips_floor_offset", text="Sticky Lips Offset")   
            col_m.prop(self, 'jaw_speed', text='Jaw Speed Fac')
            col_m.prop(self, "facial_teeth", text="Teeth")
            col_m.prop(self, "facial_tongue", text="Tongue")
            
            col_f.prop(self, 'facial_chins', text='Chins')            
            
            
            if scn.arp_experimental_mode:
                col_exp = col_f.column()
                col_exp.separator()
                col_exp.label(text="Experimental:")
                col_exp.prop(self, "eyebrows_type", text="Eyebrows Type")
                col_exp.prop(self, "lips_offset", text="Lips Offset")
                col_exp.prop(self, "lips_big_masters", text="Lips Masters Frequency")
                col_exp.prop(self, "lips_corner_offset", text="Lips Corners Offset")
                col_exp.prop(self, "jaw_rotation", text="Rotate Jaw")
                col_exp.prop(self, "jaw_separate_location", text="Separate Jaw Location")

            layout.separator()
        elif self.limb_type == "ear":
            layout.prop(self, 'ear_count', text="Count")
            layout.separator()
        elif self.limb_type == "arm":
            col = layout.column()
            col.prop(self, 'arm_fk_lock', text="Arm FK Lock-Free")
            col = layout.column()
            col.enabled = (rig.arp_secondary_type != "BENDY_BONES")
            col.prop(self, "arm_twist_bones", text="Twist Bones")
            col.prop(self, "arm_ikpole_distance", text="IK Pole Distance")
            
            col = layout.column()
            if rig.arp_secondary_type == "BENDY_BONES":
                col.prop(self, "arm_bbones_ease_out", text="Drive Bbones Ease Out")
                
            col.separator()
            
            row = col.row(align=True).split(factor=0.45)
            row.label(text="IK-FK Default:")
            row.prop(self, "arm_ikfk_default", text="")
            
            row = col.row(align=True)
            row.prop(self, "arm_softik", text="Soft IK")
            row = col.row(align=True)
            row.prop(self, "arm_auto_ik_roll", text="Auto IK Roll")
            
            row = col.row(align=True).split(factor=0.45)
            row.label(text="Rot. Fingers from Scale:")
            row.prop(rig, "rig_fingers_rot", text="")
            
            row = col.row(align=True).split(factor=0.45)
            row.label(text="Rot. Thumb from Scale:")
            row.prop(rig, "rig_fingers_rot_thumb", text="")            
            
            row = col.row(align=True).split(factor=0.45)
            row.label(text="Fingers Shapes:")
            row.prop(rig, "arp_fingers_shape_style", text="")

            layout.separator()

            layout.label(text="Fingers:")
            row = layout.row()
            row.prop(self, "finger_thumb", text="Thumb")
            row = layout.row()
            row.prop(self, "finger_index", text="Index")
            row.prop(self, "finger_middle", text="Middle")
            row.prop(self, "finger_ring", text="Ring")
            row.prop(self, "finger_pinky", text="Pinky")

            col = layout.column()
            col.prop(self, "fingers_ik", text="Fingers IK-FK")
            if self.fingers_ik:
                col = layout.column()
                row = col.row(align=True).split(factor=0.45)
                row.label(text="  IK Parent:")
                row.prop(self, "fingers_ik_parent", text="")
                row = col.row(align=True).split(factor=0.45)
                row.label(text="  Pole Parent:")
                row.prop(self, "fingers_ik_pole_parent", text="")

                row = col.row(align=True).split(factor=0.45)
                row.label(text="  IK Root Shape:")
                row2 = row.row(align=True).split(factor=0.8, align=True)
                row2.prop(self, "fingers_ik2_shape", text="")
                row2.prop(self, "fingers_ik2_color", text="")

                row = col.row(align=True).split(factor=0.45)
                row.label(text="  IK Tip Shape:")
                row2 = row.row(align=True).split(factor=0.8, align=True)
                row2.prop(self, "fingers_ik_shape", text="")
                row2.prop(self, "fingers_ik_color", text="")

                row = col.row(align=True).split(factor=0.45)
                row.label(text="  Pole Shape:")
                row2 = row.row(align=True).split(factor=0.8, align=True)
                row2.prop(self, "fingers_ik_pole_shape", text="")
                row2.prop(self, "fingers_ik_pole_color", text="")

                row = col.row(align=True).split(factor=0.45)
                row.label(text="  IK Pole Distance")
                row.prop(self, "fingers_ik_pole_distance", text="")
                layout.separator()

            if scn.arp_experimental_mode:
                layout.prop(self, "finger_pinky_independent", text="Independent Pinky Base (Experimental)")

            if scn.arp_experimental_mode:
                layout.separator()
                col = layout.column()
                col.prop(self, "hand_ik_offset", text="IK Offset Controller")

            # if scn.arp_debug_mode:
            layout.separator()
            layout.prop(self, "arm_wings", text="Wings")
            if self.arm_wings:
                layout.prop(self, "arm_feathers", text="Arm Feathers")
                layout.prop(self, "forearm_feathers", text="Forearm Feathers")
                layout.prop(self, "hand_feathers", text="Hand Feathers")
                layout.prop(self, "feathers_subdiv", text="Feather Subdivisions")
                layout.prop(self, "feathers_layers", text="Feather Layers")
                layout.prop(self, "feathers_update_transforms", text="Update Existing Feathers Transforms")
                layout.prop(self, "feathers_parent_layers", text="Parent Feathers Layers")
                layout.prop(self, "feathers_fold_controller", text="Add Wings Fold Controller")

            layout.separator()
        elif self.limb_type == "leg":
            col = layout.column()
            col.enabled = (rig.arp_secondary_type != "BENDY_BONES")
            col.prop(self, "leg_twist_bones", text="Twist Bones")
            
            col.separator()
            
            row = col.row(align=True).split(factor=0.45)
            row.label(text="  IK-FK Default:")
            row.prop(self, "leg_ikfk_default", text="")     
            
            col = layout.column()
            
            if rig.arp_secondary_type == "BENDY_BONES":
                col.prop(self, "leg_bbones_ease_out", text="Drive Bbones Ease Out")
            layout.prop(self, "leg_softik", text="Soft IK")           
            layout.prop(self, "leg_auto_ik_roll", text="Auto IK Roll")
            layout.prop(self, "three_bones_leg", text="3 Bones Leg")
            layout.label(text="Toes:")
            row = layout.row()
            row.prop(self, "toes_thumb", text="Thumb")
            row.prop(self, "toes_index", text="Index")
            row = layout.row()
            row.prop(self, "toes_middle", text="Middle")
            row.prop(self, "toes_ring", text="Ring")
            row = layout.row()
            row.prop(self, "toes_pinky", text="Pinky")
            layout.separator()
            col = layout.column()
            col.prop(self, "toes_pivot", text="Toes Pivot Controller")
            col.prop(self, "foot_ik_offset", text="IK Offset Controller")
            col.prop(self, "leg_ikpole_distance", text="IK Pole Distance")
            col.prop(self, "leg_foot_roll_distance", text="Roll Cursor Distance")
            col.prop(self, "leg_foot_roll_fac", text="Roll Cursor Factor")
            layout.separator()
        elif self.limb_type == "spline_ik":
            layout.prop(self, "spline_type", text="Type", expand=True)
            layout.separator()
            layout.prop(self, "spline_count", text="IK Spline Count")
            if self.spline_type == "2":
                layout.prop(self, "spline_cont_freq", text="Controllers Frequency")
                layout.prop(self, "spline_interpolation", text="Interpolation")
            layout.prop(self, "spline_bendy", text="Bendy Bones Count")
            layout.prop(self, "spline_smoothness", text="Curve Smoothness")

            layout.separator()
            if self.spline_type == "1":
                layout.label(text="End Controller Parent:")
            elif self.spline_type == "2":
                layout.label(text="c_spline_master parent:")
                layout.prop(self, "spline_parent_master", text="")
                layout.label(text="c_spline_master tip parent:")
            layout.prop(self, "spline_parent_last", text="")
            layout.label(text="c_spline_tip parent:")
            layout.prop(self, "spline_parent_last_master", text="")
            layout.separator()
            layout.prop(self, 'spline_name', text="Name")
            layout.prop(self, 'spline_side', text="Side")
            layout.prop(self, 'spline_deform', text="Deform")
            layout.prop(self, 'spline_update_transforms', text="Update Transforms")
            layout.separator()
        elif self.limb_type == "bbones":
            layout.prop(self, "bbones_count", text="Bendy Bones Count")
            layout.prop(self, "bbones_segments", text="Bendy Bones Segments")
            layout.prop(self, "bbones_scale", text="Controllers Scale")
            layout.separator()
            layout.prop(self, 'bbones_name', text='Name')
            layout.prop(self, 'bbones_side', text="Side")

        else:
            layout.label(text="This limb has no parameters")  # , icon = 'INFO')


    def execute(self, context):
        if self.limb_type == "tail":            
            set_tail(self.tail_count, master_at_root=self.tail_master_at_root, new_side=self.tail_side, update_transforms=self.tail_update_transforms)
        elif self.limb_type == 'ear':
            set_ears(self.ear_count)
        elif self.limb_type == 'neck':
            set_neck(self.neck_count, twist=self.neck_twist, bendy_segments=self.neck_bendy)
        elif self.limb_type == 'arm':
            set_fingers(self.finger_thumb, self.finger_index, self.finger_middle, self.finger_ring, self.finger_pinky, independent_pinky=self.finger_pinky_independent, fingers_ik=self.fingers_ik, fingers_ik_shape=self.fingers_ik_shape, fingers_ik_color=self.fingers_ik_color, fingers_ik2_shape=self.fingers_ik2_shape, fingers_ik2_color=self.fingers_ik2_color, fingers_ik_parent=self.fingers_ik_parent, fingers_ik_pole_parent=self.fingers_ik_pole_parent, fingers_ik_pole_shape=self.fingers_ik_pole_shape, fingers_ik_pole_color=self.fingers_ik_pole_color, fingers_ik_pole_distance=self.fingers_ik_pole_distance)
            set_arm_ikpole_distance(self.arm_ikpole_distance)
            set_arm_twist(self.arm_twist_bones, self.side, bbones_ease_out=self.arm_bbones_ease_out)
            set_arm_feathers(self.arm_wings, self.arm_feathers, self.forearm_feathers, self.hand_feathers,
                             self.feathers_layers, self.feathers_subdiv, self.feathers_update_transforms,
                             self.feathers_parent_layers, self.feathers_fold_controller, self.side)
            set_arm_ik_offset(self.hand_ik_offset)
            set_arm_fk_lock(self.arm_fk_lock)
            set_arm_auto_ik_roll(self.arm_auto_ik_roll)
            set_arm_softik(self.arm_softik)            
            set_arm_ikfk_default(self.arm_ikfk_default)
            
        elif self.limb_type == 'leg':
            set_toes(self.toes_thumb, self.toes_index, self.toes_middle, self.toes_ring, self.toes_pinky)
            set_toes_pivot(self.toes_pivot)
            set_leg_ikpole_distance(self.leg_ikpole_distance)
            set_leg_roll_cursor_distance(self.leg_foot_roll_distance, self.leg_foot_roll_fac)
            set_leg_twist(self.leg_twist_bones, self.side, bbones_ease_out=self.leg_bbones_ease_out)
            set_leg_ik_offset(self.foot_ik_offset)
            set_three_bones_leg(self.three_bones_leg)
            set_leg_auto_ik_roll(self.leg_auto_ik_roll)
            set_leg_softik(self.leg_softik)            
            set_leg_ikfk_default(self.leg_ikfk_default)
        elif self.limb_type == 'head':
            set_facial(enable=self.facial, mouth_enabled=self.facial_mouth, auto_lips=self.auto_lips, auto_lips_visual=self.auto_lips_visual, 
                       lips_floor=self.lips_floor, lips_floor_offset=self.lips_floor_offset, #lips_floor_full=self.lips_floor_full,
                       lips_offset=self.lips_offset, lips_corner_offset=self.lips_corner_offset, teeth_enabled=self.facial_teeth, tongue_enabled=self.facial_tongue,
                       eyebrows_type=self.eyebrows_type, lips_big_masters=self.lips_big_masters,
                       eyelids_align=self.eyelid_align_rot, eyelid_speed=self.eyelid_speed_fac, eyelids_amount=self.eyelids_amount, 
                       enable_eyelids_tweak=self.enable_eyelids_tweak, eyelids_updt_transf=self.eyelids_updt_transf,
                       skulls_align=self.skulls_align, skull_bones=self.skull_bones, chins_enabled=self.facial_chins, 
                       noses_enabled=self.facial_noses, eye_l_enabled=self.facial_eye_l, eye_r_enabled=self.facial_eye_r, 
                       eyebrow_l_enabled=self.facial_eyebrow_l, eyebrow_r_enabled=self.facial_eyebrow_r, cheeks_enabled=self.facial_cheeks, lips_amount=self.lips_amount, 
                       lips_masters=self.lips_masters, lips_masters_linear=self.lips_masters_linear, lips_updt_transf=self.lips_updt_transf, 
                       lips_soft_lin_corner=self.lips_soft_lin_corner, lips_soft_limit_corner=self.lips_soft_limit_corner, lips_soft_lin_jaw=self.lips_soft_lin_jaw, 
                       lips_soft_limit_jaw=self.lips_soft_limit_jaw, jaw_speed=self.jaw_speed, lips_roll_cns=self.lips_roll_cns, lips_roll_speed=self.lips_roll_speed)
            if self.facial:
                set_jaw_rotation_location(self.jaw_rotation, self.auto_lips_visual, self.jaw_separate_location)
                set_eyetargets_distance(self.eye_target_dist)
        elif self.limb_type == 'spine':
            set_spine(bottom=self.bottom, align_root_master=self.align_root_master, spine_master_enabled=self.spine_master, spine_master_space=self.spine_master_space, spine_master_stretchy=self.spine_master_stretchy)
        elif self.limb_type == 'spline_ik':
            set_spline_ik(self.spline_count, type=self.spline_type, cont_freq=self.spline_cont_freq, interpolation=self.spline_interpolation, bbones_count=self.spline_bendy,
                          spline_parent_master=self.spline_parent_master, spline_parent_last=self.spline_parent_last, spline_parent_last_master=self.spline_parent_last_master,
                          side_arg=self.side, new_name=self.spline_name,
                          new_side=self.spline_side, deform=self.spline_deform, smoothness=self.spline_smoothness, update_transforms=self.spline_update_transforms)
        elif self.limb_type == "bbones":
            set_bendy_bones(self.bbones_count, bbones_segment_args=self.bbones_segments, scale=self.bbones_scale,
                            side_arg=self.side, new_side=self.bbones_side, new_name=self.bbones_name)


        # make sure to enable default settings in next execution
        self.reset_to_default_settings = True

        return {'FINISHED'}

    
    def invoke(self, context, event):
        scn = context.scene
        # Get the selected bone limb type
        sel_bone = context.selected_editable_bones[0]
        sel_bone_name = sel_bone.name
        split_name = sel_bone_name.split('_')
        self.side = get_bone_side(sel_bone_name)

        arm_bones_ref = ["shoulder", "arm", "forearm", "hand", "index1", "index2", "index3", "thumb1", "thumb2",
                         "thumb3", "middle1", "middle2", "middle3", "ring1", "ring2", "ring3", "pinky1", "pinky2",
                         "pinky3"]
        leg_bones_ref = ["thigh", "leg", "foot", "toes"]

        # Read saved settings for the selected limb
        if get_edit_bone(sel_bone_name).layers[17]:# reference bones only
            # IK splines
            if split_name[0] == "spline" or (sel_bone.keys() and "arp_spline" in sel_bone.keys()):
                self.limb_type = "spline_ik"

                spline_name = "spline"
                if sel_bone.keys() and "arp_spline" in sel_bone.keys():
                    spline_name = sel_bone['arp_spline']

                spline_root = get_edit_bone(spline_name+"_01_ref"+self.side)

                if len(spline_root.keys()):
                    if "spline_type" in spline_root.keys():
                        self.spline_type = spline_root["spline_type"]
                    if "spline_count" in spline_root.keys():
                        self.spline_count = spline_root["spline_count"]
                    if "spline_cont_freq" in spline_root.keys():
                        self.spline_cont_freq = spline_root["spline_cont_freq"]
                    if "spline_interpolation" in spline_root.keys():
                        self.spline_interpolation = spline_root["spline_interpolation"]
                    if "spline_bbones" in spline_root.keys():
                        self.spline_bendy = spline_root["spline_bbones"]
                    if "spline_parent_master" in spline_root.keys():
                        self.spline_parent_master = spline_root["spline_parent_master"]
                    if "spline_parent_last" in spline_root.keys():
                        self.spline_parent_last = spline_root["spline_parent_last"]
                    if "spline_parent_last_master" in spline_root.keys():
                        self.spline_parent_last_master = spline_root["spline_parent_last_master"]
                    if "spline_name" in spline_root.keys():
                        self.spline_name = spline_root["spline_name"]
                    if "spline_smoothness" in spline_root.keys():
                        self.spline_smoothness = spline_root["spline_smoothness"]
                    if "spline_deform" in spline_root.keys():
                        self.spline_deform = spline_root["spline_deform"]
                    if "spline_update_transforms" in spline_root.keys():
                        self.spline_update_transforms = spline_root["spline_update_transforms"]
                # evaluate spline ik side
                if sel_bone_name.endswith(".x"):
                    self.spline_side = ".x"
                elif sel_bone_name.endswith(".l"):
                    self.spline_side = ".l"
                elif sel_bone_name.endswith(".r"):
                    self.spline_side = ".r"

            # bendy bones
            elif split_name[0] == "bbones" or (sel_bone.keys() and "arp_bbones" in sel_bone.keys()):
                self.limb_type = "bbones"

                bbones_name = "bbones"
                if sel_bone.keys() and "arp_bbones" in sel_bone.keys():
                    bbones_name = sel_bone['arp_bbones']

                bbones_root = get_edit_bone(bbones_name+"_01_ref"+self.side)
                if len(bbones_root.keys()):
                    if "bbones_count" in bbones_root.keys():
                        self.bbones_count = bbones_root["bbones_count"]
                    if "bbones_segments" in bbones_root.keys():
                        self.bbones_segments = bbones_root["bbones_segments"]
                    if "bbones_scale" in bbones_root.keys():
                        self.bbones_scale = bbones_root["bbones_scale"]
                    if "bbones_name" in bbones_root.keys():
                        self.bbones_name = bbones_root["bbones_name"]

                # evaluate spline ik side
                if sel_bone_name.endswith(".x"):
                    self.bbones_side = ".x"
                elif sel_bone_name.endswith(".l"):
                    self.bbones_side = ".l"
                elif sel_bone_name.endswith(".r"):
                    self.bbones_side = ".r"
                    
            # spine
            elif split_name[0] == 'root' or split_name[0] == 'spine':
                self.limb_type = "spine"
                self.bottom = bool(get_edit_bone("bot_bend_ref" + self.side.replace('.x', '.l')))
                root = get_edit_bone("root_ref.x")
                self.align_root_master = True
                if len(root.keys()):
                    if "align_root_master" in root.keys():
                        self.align_root_master = root["align_root_master"]
                    if 'spine_master' in root.keys():
                        self.spine_master = root['spine_master']
                    if 'spine_master_space' in root.keys():
                        self.spine_master_space = root['spine_master_space']
                    if 'spine_master_stretchy' in root.keys():
                        self.spine_master_stretchy = root['spine_master_stretchy']

            # tail
            elif split_name[0] == 'tail':
                self.limb_type = "tail"
                # evaluate tail master at root
                tail_00_ref = get_edit_bone("tail_00_ref"+self.side)
                if tail_00_ref:
                    if "master_at_root" in tail_00_ref.keys():
                        self.tail_master_at_root = tail_00_ref.get("master_at_root")
                    if "tail_count" in tail_00_ref.keys():
                        self.tail_count = tail_00_ref.get("tail_count")
                    if "tail_side" in tail_00_ref.keys():
                        self.tail_side = tail_00_ref.get("tail_side")
                    if "tail_update_transforms" in tail_00_ref.keys():
                        self.tail_update_transforms = tail_00_ref.get("tail_update_transforms")

            # neck
            elif split_name[0] == 'neck' or split_name[0] == 'subneck':
                self.limb_type = 'neck'
                neck_ref = get_edit_bone("neck_ref" + self.side)
                if "neck_count" in neck_ref.keys():
                    self.neck_count = neck_ref["neck_count"]
                if "neck_twist" in neck_ref.keys():
                    self.neck_twist = neck_ref["neck_twist"]
                if "neck_bendy" in neck_ref.keys():
                    self.neck_bendy = neck_ref["neck_bendy"]

            # head
            elif split_name[0] == 'head':
                self.limb_type = 'head'
                
                head_ref = get_edit_bone("head_ref" + self.side)
                
                # evaluate the facial bones
                if 'facial' in head_ref.keys():
                    self.facial = head_ref['facial']
                else:#backward-compatibility test
                    self.facial = bool(get_edit_bone("jaw_ref" + self.side))
                    
                # skull bones
                if "skull_bones" in head_ref.keys():
                    self.skull_bones = head_ref["skull_bones"]
                else:#backward-compatibility, was enabled by default
                    self.skull_bones = True

                if self.facial:
                    # evaluate current facial settings based on current setup to fix
                    # add missing properties of older rigs
                    #   jaw rotation
                    if not 'arp_jaw_rotation' in head_ref.keys():
                        bpy.ops.object.mode_set(mode='POSE')
                        jaw_pbone = get_pose_bone("jawbone"+self.side)
                        cns = jaw_pbone.constraints.get("Damped Track")
                        enabled = False
                        if cns:
                            if cns.influence == 0.0:
                                enabled = True
                        bpy.ops.object.mode_set(mode='EDIT')
                        head_ref = get_edit_bone("head_ref" + self.side)
                        head_ref["arp_jaw_rotation"] = enabled

                    #   jaw location
                    if not 'arp_jaw_location' in head_ref.keys():
                        bpy.ops.object.mode_set(mode='POSE')
                        jaw_pbone = get_pose_bone("jawbone" + self.side)
                        cns_loc = jaw_pbone.constraints.get("jawbone" + self.side)
                        enabled = True if cns_loc else False
                        bpy.ops.object.mode_set(mode='EDIT')
                        head_ref = get_edit_bone("head_ref" + self.side)
                        head_ref['arp_jaw_location'] = enabled

                    #   auto lips visual
                    if not "auto_lips_visual" in head_ref.keys():
                        follow_bone_name = "lips_top_follow"+self.side[:-2]+".l"
                        follow_bone = get_edit_bone(follow_bone_name)
                        enabled = True if follow_bone else False
                        head_ref['auto_lips_visual'] = enabled

                    #   lips offset
                    if not "lips_offset" in head_ref.keys():
                        lips_offset_b = get_edit_bone("c_lips_offset" + self.side)
                        enabled = True if lips_offset_b else False
                        head_ref["lips_offset"] = enabled

                    #   lips masters
                    if not "lips_big_masters" in head_ref.keys():
                        master_top_name = "c_lips_top_big_master" + self.side
                        master_top_ref = get_edit_bone(master_top_name)
                        enabled = True if master_top_ref else False
                        head_ref["lips_big_masters"] = enabled

                    #   lips corner offset
                    if not "lips_corner_offset" in head_ref.keys():
                        lips_cor_mid_name = "lips_corner_middle" + self.side
                        lips_cor_mid = get_edit_bone(lips_cor_mid_name)
                        enabled = True if lips_cor_mid else False
                        head_ref["lips_corner_offset"] = enabled                        
                    
                    #   eyebrows type
                    if not "eyebrows_type" in head_ref.keys():
                        eyebrows_type = 1
                        eyebrow_end_target = get_edit_bone("eyebrow_01_end_target"+self.side[:-2]+".l")
                        if eyebrow_end_target:
                            eyebrows_type = 2
                        eyeb_offset_full = get_edit_bone('c_eyebrow_offset_full'+self.side[:-2]+".l")
                        if eyeb_offset_full:
                            eyebrows_type = 3
                        head_ref["eyebrows_type"] = eyebrows_type

                bpy.ops.object.mode_set(mode='EDIT')
                head_ref = get_edit_bone("head_ref" + self.side)

                if 'arp_jaw_rotation' in head_ref.keys():
                    self.jaw_rotation = head_ref['arp_jaw_rotation']
                if 'arp_jaw_location' in head_ref.keys():
                    self.jaw_separate_location = head_ref['arp_jaw_location']
                if "eye_target_dist" in head_ref.keys():
                    self.eye_target_dist = head_ref['eye_target_dist']
                if 'jaw_speed' in head_ref.keys():
                    self.jaw_speed = head_ref['jaw_speed']
                if 'lips_amount' in head_ref.keys():
                    self.lips_amount = head_ref['lips_amount']
                if 'lips_masters' in head_ref.keys():
                    self.lips_masters = head_ref['lips_masters']
                if 'lips_masters_linear' in head_ref.keys():
                    self.lips_masters_linear = head_ref['lips_masters_linear']
                if 'lips_roll_cns' in head_ref.keys():
                    self.lips_roll_cns = head_ref['lips_roll_cns']
                if 'lips_roll_speed' in head_ref.keys():
                    self.lips_roll_speed = head_ref['lips_roll_speed']
                if scn.arp_retro_lips:
                    self.lips_amount = 2
                if 'lips_soft_lin_corner' in head_ref.keys():
                    self.lips_soft_lin_corner = head_ref['lips_soft_lin_corner']
                if 'lips_soft_lin_jaw' in head_ref.keys():
                    self.lips_soft_lin_jaw = head_ref['lips_soft_lin_jaw']
                if 'lips_updt_transf' in head_ref.keys():
                    self.lips_updt_transf = head_ref['lips_updt_transf']
                if "auto_lips" in head_ref.keys():
                    self.auto_lips = head_ref["auto_lips"]
                if "lips_soft_limit_corner" in head_ref.keys():
                    self.lips_soft_limit_corner = head_ref["lips_soft_limit_corner"]
                if "lips_soft_limit_jaw" in head_ref.keys():
                    self.lips_soft_limit_jaw = head_ref["lips_soft_limit_jaw"]
                if "auto_lips_visual" in head_ref.keys():
                    self.auto_lips_visual = head_ref["auto_lips_visual"]
                if "lips_floor" in head_ref.keys():
                    self.lips_floor = head_ref["lips_floor"]
                #if "lips_floor_full" in head_ref.keys():
                #    self.lips_floor_full = head_ref["lips_floor_full"]
                if "lips_floor_offset" in head_ref.keys():
                    self.lips_floor_offset = head_ref["lips_floor_offset"]
                if "lips_offset" in head_ref.keys():
                    self.lips_offset = head_ref["lips_offset"]
                if "lips_big_masters" in head_ref.keys():
                    self.lips_big_masters = head_ref["lips_big_masters"]
                if "lips_corner_offset" in head_ref.keys():
                    self.lips_corner_offset = head_ref["lips_corner_offset"]
                if 'enable_eyelids_tweak' in head_ref.keys():
                    self.enable_eyelids_tweak = head_ref['enable_eyelids_tweak']
                if 'eyelids_amount' in head_ref.keys():
                    self.eyelids_amount = head_ref['eyelids_amount']
                if 'eyelids_updt_transf' in head_ref.keys():
                    self.eyelids_updt_transf = head_ref['eyelids_updt_transf']
                if "eyelid_align_rot" in head_ref.keys():
                    self.eyelid_align_rot = head_ref["eyelid_align_rot"]
                if "eyelid_speed_fac" in head_ref.keys():
                    self.eyelid_speed_fac = head_ref["eyelid_speed_fac"]
                if "skulls_align" in head_ref.keys():
                    self.skulls_align = head_ref["skulls_align"]
                if "eyebrows_type" in head_ref.keys():
                    self.eyebrows_type = "type_" + str(head_ref["eyebrows_type"])
                if 'mouth_enabled' in head_ref.keys():
                    self.facial_mouth = head_ref['mouth_enabled']   
                if 'teeth_enabled' in head_ref.keys():
                    self.facial_teeth = head_ref['teeth_enabled']
                if 'tongue_enabled' in head_ref.keys():
                    self.facial_tongue = head_ref['tongue_enabled']
                if 'chins_enabled' in head_ref.keys():
                    self.facial_chins = head_ref['chins_enabled']
                if 'noses_enabled' in head_ref.keys():
                    self.facial_noses = head_ref['noses_enabled']
                if 'eye_l_enabled' in head_ref.keys():
                    self.facial_eye_l = head_ref['eye_l_enabled']
                if 'eye_r_enabled' in head_ref.keys():
                    self.facial_eye_r = head_ref['eye_r_enabled']
                if 'eyebrow_l_enabled' in head_ref.keys():
                    self.facial_eyebrow_l = head_ref['eyebrow_l_enabled']
                if 'eyebrow_r_enabled' in head_ref.keys():
                    self.facial_eyebrow_r = head_ref['eyebrow_r_enabled']
                if 'cheeks_enabled' in head_ref.keys():
                    self.facial_cheeks = head_ref['cheeks_enabled']
                    
            # ear
            elif split_name[0] == 'ear':
                self.limb_type = 'ear'

            # arm
            elif split_name[0] in arm_bones_ref:
                self.limb_type = 'arm'

                # evaluate the current fingers
                hand = get_edit_bone("hand"+self.side)
                hand_ref = get_edit_bone("hand_ref"+self.side)

                if hand:
                    children = [child.name.split('_')[1] for child in hand.children if '_base' in child.name]
                    self.finger_thumb = "thumb1" in children
                    self.finger_index = "index1" in children
                    self.finger_middle = "middle1" in children
                    self.finger_ring = "ring1" in children
                    self.finger_pinky = "pinky1" in children

                if hand_ref:
                    if 'ikfk_default' in hand_ref.keys():
                        self.arm_ikfk_default = hand_ref['ikfk_default']
                        
                    if "fingers_ik" in hand_ref.keys():
                        self.fingers_ik = hand_ref["fingers_ik"]
                    else:
                        self.fingers_ik = False                    

                    if "fingers_ik_shape" in hand_ref.keys():
                        self.fingers_ik_shape = hand_ref["fingers_ik_shape"]
                    if "fingers_ik2_shape" in hand_ref.keys():
                        self.fingers_ik2_shape = hand_ref["fingers_ik2_shape"]
                    if "fingers_ik_pole_shape" in hand_ref.keys():
                        self.fingers_ik_pole_shape = hand_ref["fingers_ik_pole_shape"]
                    if "fingers_ik_parent" in hand_ref.keys():
                        self.fingers_ik_parent = hand_ref["fingers_ik_parent"]
                    if "fingers_ik_pole_parent" in hand_ref.keys():
                        self.fingers_ik_pole_parent = hand_ref["fingers_ik_pole_parent"]
                    if "fingers_ik_pole_distance" in hand_ref.keys():
                        self.fingers_ik_pole_distance = hand_ref["fingers_ik_pole_distance"]
                    try:# error when set to None
                        if "fingers_ik_pole_color" in hand_ref.keys():
                            self.fingers_ik_pole_color = hand_ref["fingers_ik_pole_color"]
                        if "fingers_ik_color" in hand_ref.keys():
                            self.fingers_ik_color = hand_ref["fingers_ik_color"]
                        if "fingers_ik2_color" in hand_ref.keys():
                            self.fingers_ik2_color = hand_ref["fingers_ik2_color"]
                    except:
                        pass

                
                arm_ref = get_edit_bone("arm_ref" + self.side)
                if arm_ref:
                    if len(arm_ref.keys()):                        
                        soft_ik_prop_name = ard.arm_props['soft_ik']
                        if soft_ik_prop_name in arm_ref.keys():
                            self.arm_softik = arm_ref[soft_ik_prop_name]
                           
                        auto_ik_roll_name = ard.arm_props['auto_ik_roll']
                        if auto_ik_roll_name in arm_ref.keys():
                            self.arm_auto_ik_roll = arm_ref[auto_ik_roll_name]
                        else:
                            self.arm_auto_ik_roll = True
                            
                        if 'arm_fk_lock' in arm_ref.keys():
                            self.arm_fk_lock = arm_ref['arm_fk_lock']
                        else:
                            self.arm_fk_lock = False
                            
                        if 'twist_bones_amount' in arm_ref.keys():
                            self.arm_twist_bones = arm_ref['twist_bones_amount']
                        else:
                            arm_ref['twist_bones_amount'] = 1
                        # ease out
                        if 'arp_bbones_ease_out' in arm_ref.keys():
                            self.arm_bbones_ease_out = arm_ref['arp_bbones_ease_out']
                        else:
                            arm_ref['arp_bbones_ease_out'] = True

                            # evaluate the current feathers
                        if 'arp_feathers' in arm_ref.keys():
                            self.arm_feathers = arm_ref['arp_feathers']
                        else:
                            self.arm_feathers = 1
                            self.arm_wings = False

                        if 'arp_wings' in arm_ref.keys():
                            self.arm_wings = arm_ref['arp_wings']
                        if 'arp_feathers_subdiv' in arm_ref.keys():
                            self.feathers_subdiv = arm_ref['arp_feathers_subdiv']
                        if 'arp_feathers_layers' in arm_ref.keys():
                            self.feathers_layers = arm_ref['arp_feathers_layers']
                        if 'arp_feathers_layers_parent' in arm_ref.keys():
                            self.feathers_parent_layers = arm_ref['arp_feathers_layers_parent']
                        if 'arp_feathers_update' in arm_ref.keys():
                            self.feathers_update_transforms = arm_ref['arp_feathers_update']
                        if 'arp_feathers_fold_controller' in arm_ref.keys():
                            self.feathers_fold_controller = arm_ref['arp_feathers_fold_controller']

                forearm_ref = get_edit_bone("forearm_ref" + self.side)
                if forearm_ref:
                    if len(forearm_ref.keys()) > 0:
                        if 'arp_feathers' in forearm_ref.keys():
                            self.forearm_feathers = forearm_ref['arp_feathers']
                        else:
                            self.forearm_feathers = 1
                            self.arm_wings = False

                if hand_ref:
                    if len(hand_ref.keys()) > 0:
                        if 'arp_feathers' in hand_ref.keys():
                            self.hand_feathers = hand_ref['arp_feathers']
                        else:
                            self.hand_feathers = 1
                            self.arm_wings = False

                # evaluate the ik offset
                ik_offset_bone = get_edit_bone("c_hand_ik_offset" + self.side)
                self.hand_ik_offset = True if ik_offset_bone else False

                # evaluate the ik pole distance
                if 'ik_pole_distance' in hand_ref.keys():
                    self.arm_ikpole_distance = hand_ref['ik_pole_distance']

            # legs
            elif split_name[0] in leg_bones_ref:
                self.limb_type = 'leg'
                
                # evaluate 3 bones leg
                thigh_b_ref = get_edit_bone("thigh_b_ref" + self.side)
                self.three_bones_leg = True if thigh_b_ref else False

                # evaluate the current toes
                toes_ref = get_edit_bone("toes_ref"+self.side)
                if toes_ref:
                    children = [child.name[:10] for child in toes_ref.children]
                    self.toes_thumb = "toes_thumb" in children
                    self.toes_index = "toes_index" in children
                    self.toes_middle = "toes_middl" in children
                    self.toes_ring = "toes_ring1" in children
                    self.toes_pinky = "toes_pinky" in children
                    
                thigh_ref_name = ard.leg_ref_bones_dict['thigh']
                thigh_ref = get_edit_bone(thigh_ref_name+self.side)
                self.leg_twist_bones = 1
                
                if thigh_ref:
                    if len(thigh_ref.keys()):                        
                        # evaluate the twist bones
                        if 'twist_bones_amount' in thigh_ref.keys():
                            self.leg_twist_bones = thigh_ref['twist_bones_amount']
                        else:
                            thigh_ref['twist_bones_amount'] = 1

                        # ease out
                        if 'arp_bbones_ease_out' in thigh_ref.keys():
                            self.leg_bbones_ease_out = thigh_ref['arp_bbones_ease_out']
                        else:
                            thigh_ref['arp_bbones_ease_out'] = True
                            
                        # soft ik
                        soft_ik_prop_name = ard.leg_props['soft_ik']
                        if soft_ik_prop_name in thigh_ref.keys():
                            self.leg_softik = thigh_ref[soft_ik_prop_name]
                        
                        # auto ik roll
                        auto_ik_roll_name = ard.leg_props['auto_ik_roll']
                        if auto_ik_roll_name in thigh_ref.keys():
                            self.leg_auto_ik_roll = thigh_ref[auto_ik_roll_name]
                        else:
                            self.leg_auto_ik_roll = True

                # evaluate the toes pivot
                toes_pivot_bone = get_edit_bone("c_toes_pivot" + self.side)
                self.toes_pivot = True if toes_pivot_bone else False

                # evaluate the ik offset
                ik_offset_bone = get_edit_bone("c_foot_ik_offset" + self.side)
                self.foot_ik_offset = True if ik_offset_bone else False

                # evaluate the ik pole distance
                foot_ref = get_edit_bone("foot_ref" + self.side)
                self.leg_ikpole_distance = 1.0
                if 'ik_pole_distance' in foot_ref.keys():
                    self.leg_ikpole_distance = foot_ref['ik_pole_distance']

                # evaluate the roll cursor distance
                self.leg_foot_roll_distance = 1.0
                if 'roll_cursor_distance' in foot_ref.keys():
                    self.leg_foot_roll_distance = foot_ref['roll_cursor_distance']
                self.leg_foot_roll_fac = 1.0
                if 'roll_cursor_fac' in foot_ref.keys():
                    self.leg_foot_roll_fac = foot_ref['roll_cursor_fac']
                    
                # evaluate ik-fk default value
                if 'ikfk_default' in foot_ref.keys():
                    self.leg_ikfk_default = foot_ref['ikfk_default']

            
            else:
                self.limb_type = ""

        else:
            self.report({"WARNING"}, "Select a reference bone")
            return {'FINISHED'}

        # Open dialog
        wm = context.window_manager
        return wm.invoke_props_dialog(self)


class ARP_OT_show_retro_options(Operator):
    """Show backward-compatibility options, applies after Match to Rig"""

    bl_idname = "arp.show_retro_options"
    bl_label = "Backward-compatibility options:"
    bl_options = {'UNDO'}

    def draw(self, context):
        scn = context.scene
        layout = self.layout
        layout.prop(scn, "arp_retro_constraints", text="Old Constraints")
        layout.prop(scn, 'arp_retro_axes', text='Old Arms-Feet Alignment')
        layout.prop(scn, "arp_retro_eyes", text="Old Eyes Alignment")
        layout.prop(scn, "arp_retro_feet", text="Old Feet Alignment")
        layout.prop(scn, 'arp_retro_lips', text='Old Soft Lips Constraints')
        layout.prop(scn, "arp_retro_spine_bend", text="Old Spine Bend Alignment")    
        layout.prop(scn, "arp_retro_stretch_pin", text="Old Stretch and Pin Controllers Alignment")
        layout.prop(scn, "arp_retro_splineik_quat", text="Spline IK: Quaternions")

    def invoke(self, context, event):
        # Open dialog
        wm = context.window_manager
        return wm.invoke_props_dialog(self)

    def execute(self, context):
        return {'FINISHED'}


class ARP_OT_export_data(Operator):
    """Export some rig data into file (bone transform constraints values)"""

    bl_idname = "arp.export_data"
    bl_label = "export_data"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        return is_object_arp(bpy.context.active_object)

    def execute(self, context):
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        try:
            _export_data()
            self.report({"INFO"}, "Transform constraints value exported")

        finally:
            context.preferences.edit.use_global_undo = use_global_undo

        return {'FINISHED'}


class ARP_OT_export_limbs(Operator):
    """Export the limbs as separate blend files"""

    bl_idname = "arp.export_limbs"
    bl_label = "export_limbs"
    bl_options = {'UNDO'}

    state_proxy_picker: BoolProperty(default=False)
    state_xmirror: BoolProperty(default=False)

    @classmethod
    def poll(cls, context):
        return is_object_arp(bpy.context.active_object)

    def execute(self, context):
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        try:
            _export_limbs(self)
            self.report({"INFO"}, "Limbs exported")

        finally:
            context.preferences.edit.use_global_undo = use_global_undo

        return {'FINISHED'}


class ARP_OT_remove_picker(Operator):
    """Remove the picker panel"""

    bl_idname = "arp.remove_picker"
    bl_label = "remove_picker"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        return is_object_arp(bpy.context.active_object)

    def execute(self, context):
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        try:
            _remove_picker()
            self.report({"INFO"}, "Picker removed")

        finally:
            context.preferences.edit.use_global_undo = use_global_undo

        return {'FINISHED'}


class ARP_OT_add_picker(Operator):
    """Add the picker panel"""

    bl_idname = "arp.add_picker"
    bl_label = "add_picker"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        return is_object_arp(bpy.context.active_object)

    def execute(self, context):
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        addon_directory = os.path.dirname(os.path.abspath(__file__))
        filepath = addon_directory + "/picker.py"

        try:
            add_picker_result = _add_picker(self, context, filepath, True, True)

            if add_picker_result:
                self.report({"INFO"}, "Picker generated")
            else:
                self.report({"INFO"}, "Picker already generated")

        finally:
            context.preferences.edit.use_global_undo = use_global_undo

        return {'FINISHED'}


class ARP_OT_import_picker(Operator):
    """Import the picker panel"""

    bl_idname = "arp.import_picker"
    bl_label = "Import Picker"

    filepath: StringProperty(subtype="FILE_PATH", default='py')

    @classmethod
    def poll(cls, context):
        return is_object_arp(bpy.context.active_object)

    def execute(self, context):

        try:
            file = open(self.filepath, 'rU')
            file.close()
        except:
            self.report({"ERROR"}, "Invalid file path")
            return {'FINISHED'}

        _import_picker(self.filepath, self, context)

        self.report({"INFO"}, "Picker imported")

        return {'FINISHED'}

    def invoke(self, context, event):
        self.filepath = 'picker.py'
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}


class ARP_OT_export_picker(Operator):
    """Export the picker panel"""

    bl_idname = "arp.export_picker"
    bl_label = "Export Picker"
    bl_options = {'UNDO'}

    filepath: StringProperty(subtype="FILE_PATH", default='py')

    @classmethod
    def poll(cls, context):
        return is_object_arp(bpy.context.active_object)

    def execute(self, context):
        _export_picker(self.filepath, self, context)

        self.report({"INFO"}, "Picker exported")

        return {'FINISHED'}

    def invoke(self, context, event):
        self.filepath = 'picker.py'
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}


class ARP_OT_add_muscles(Operator):
    """Add muscles bones"""

    bl_idname = "arp.add_muscles"
    bl_label = "add_muscles"
    bl_options = {'UNDO'}

    state_xmirror: BoolProperty(default=False)

    @classmethod
    def poll(cls, context):
        return bpy.context.mode == 'POSE'

    def execute(self, context):
        use_global_undo = context.user_preferences.edit.use_global_undo
        context.user_preferences.edit.use_global_undo = False

        try:
            _initialize_armature(self)

            muscles_results = _add_muscles(self)
            if muscles_results:
                self.report({"INFO"}, "Muscles bones added.")

            _finalize_armature(self)

            bpy.ops.object.mode_set(mode='POSE')

        finally:
            context.user_preferences.edit.use_global_undo = use_global_undo

        return {'FINISHED'}


class ARP_OT_remove_muscles(Operator):
    """Remove muscles bones"""

    bl_idname = "arp.remove_muscles"
    bl_label = "remove_muscles"
    bl_options = {'UNDO'}

    state_xmirror: BoolProperty(default=False)

    @classmethod
    def poll(cls, context):
        return bpy.context.mode == 'POSE'

    def execute(self, context):
        use_global_undo = context.user_preferences.edit.use_global_undo
        context.user_preferences.edit.use_global_undo = False

        try:
            _initialize_armature(self)

            muscles_results = _remove_muscles(self)
            if muscles_results:
                self.report({"INFO"}, "Muscles bones removed.")
            else:
                self.report({"INFO"}, "No muscles to remove.")

            _finalize_armature(self)

            bpy.ops.object.mode_set(mode='POSE')

        finally:
            context.user_preferences.edit.use_global_undo = use_global_undo

        return {'FINISHED'}
        
        
class ARP_OT_add_blink_pose(Operator):
    """Set the current eyelids pose as closed pose for blinks.\nThe upper or lower eyelid controller must be selected, with eyelids in a closed pose"""
    
    bl_idname = "arp.add_blink_pose"
    bl_label = "Add Blink Pose Constraints"
    bl_options = {'UNDO'}    
    
    lvl = None
    blink_action = None
    blink_actions_items = [] 
    choose_action = False
    current_action = None
    rig = None    
    cns_action = None    
    in_between_or_def: EnumProperty(items=(('IN_BETWEEN', 'As In-Between', 'Add a blink pose in-between, given the current main eyelid controller position'),
                                            ('AS_CLOSED', 'As Closed', 'Set the closed eyelids pose, given the current main eyelid controller position')),
                                            name='In Between or Def')
    
    def get_actions_items(self, context):
        return ARP_OT_add_blink_pose.blink_actions_items
        
    blink_actions: EnumProperty(items=get_actions_items, default=None)
    
    @classmethod
    def poll(cls, context):
        return bpy.context.mode == 'POSE'
        
    
    def invoke(self, context, event):
        self.rig = bpy.context.active_object
        self.cns_action = None
        self.choose_action = False
        self.blink_action = None
        
        # something selected?
        if len(get_selected_pose_bones()) == 0:
            self.report({'ERROR'}, "The upper or lower eyelid controller must be selected")
            return {'FINISHED'}

        # eyelid controller selected?
        bname = get_selected_pose_bones()[0].name
        
        if bname.startswith('c_eyelid_top'):
            self.lvl = 'top'
        elif bname.startswith('c_eyelid_bot'):
            self.lvl = 'bot'
        if self.lvl == None:
            self.report({'ERROR'}, "The upper or lower eyelid controller must be selected")
            return {'FINISHED'}

        # is "rig_blink_" action created?  
        
        # clear current list
        while len(self.blink_actions_items):
            self.blink_actions_items.pop(0)# make sure to use pop(idx) instead of remove(), buggy
            
        for act in bpy.data.actions:
            if "rig_blink_"+self.lvl in act.name:                
                self.blink_actions_items.append((act.name, act.name, ''))                
                
        if len(self.blink_actions_items) == 1:# use existing
            self.blink_action = self.blink_actions_items[0][0]
          
        elif len(self.blink_actions_items) > 1:# choose among existing actions
            self.choose_action = True            
            wm = context.window_manager
            return wm.invoke_props_dialog(self)
        
        elif len(self.blink_actions_items) == 0:# create the blink action
            act_name = 'rig_blink_'+self.lvl
            act = bpy.data.actions.new(act_name)
            self.blink_action = act_name
            
            # debug, need to assign new action to armature, otherwise update issue, cannot set the action constraint...
            if self.rig.animation_data:
                if self.rig.animation_data.action:
                    self.current_action = self.rig.animation_data.action# store current to restore later
            if self.rig.animation_data == None:
                self.rig.animation_data_create()
            self.rig.animation_data.action = act
            
        # is the constraint created?
        eyelids = []
        side = get_bone_side(bname)
        for cname in ard.get_variable_eyelids(side, type='CONTROLLER', eye_sides=[side[-2:]]):
            if self.lvl in cname:
                eyelids.append(cname)
                
        if len(eyelids) == 0:
            self.report({'ERROR'}, "No eyelids controllers found")
            return {'FINISHED'}
            
        for eyel_name in eyelids:
            pb = get_pose_bone(eyel_name)
            if len(pb.constraints):
                for cns in pb.constraints:
                    if cns.name == 'ActionBlink_'+self.lvl:                   
                        self.cns_action = cns     
                        wm = context.window_manager
                        return wm.invoke_props_dialog(self)
                        break
        
        self.execute(context)
        return {'PASS_THROUGH'}
        
    
    def draw(self, context):
        scn = context.scene
        layout = self.layout
        if self.choose_action:
            layout.label(text='Multiple blink actions found, which one should be used?')
            layout.prop(self, 'blink_actions')
            
        if self.cns_action:
            layout.prop(self, 'in_between_or_def', expand=True)
            
    
    def execute(self, context):  
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False
        
        try:
            if self.choose_action:
                self.blink_action = self.blink_actions# get the active enum
            
            _add_blink_pose(self)
            
        finally:            
            # restore action (clear if None)
            self.rig.animation_data.action = self.current_action
            
            context.preferences.edit.use_global_undo = use_global_undo
        
        mess = 'Eyelid '+self.lvl.title()+' blink pose constraints added'
        print(mess)
        #self.report({'INFO'}, mess)# doesn't work, no message displayed. Related to invoke()/draw() function? Seems to work there is no such functions
        return {'FINISHED'}
        

class ARP_OT_remove_blink_pose(Operator):
    """Remove the predefined pose constraints"""
    
    bl_idname = "arp.remove_blink_pose"
    bl_label = "Remove Blink Pose"
    bl_options = {'UNDO'}    
    lvl = None
    
    @classmethod
    def poll(cls, context):
        return bpy.context.mode == 'POSE'
        
        
    def execute(self, context):
        # something selected?
        if len(get_selected_pose_bones()) == 0:
            self.report({'ERROR'}, "The upper or lower eyelid controller must be selected")
            return {'FINISHED'}

        # eyelid controller selected?
        bname = get_selected_pose_bones()[0].name
        self.lvl = None
        if bname.startswith('c_eyelid_top'):
            self.lvl = 'top'
        elif bname.startswith('c_eyelid_bot'):
            self.lvl = 'bot'
        if self.lvl == None:
            self.report({'ERROR'}, "The upper or lower eyelid controller must be selected")
            return {'FINISHED'}
            
        _remove_blink_pose(self)
        
        return {'FINISHED'}
        
    

class ARP_OT_add_fist_ctrl(Operator):
    """Set the current fingers pose as the fist pose (or extended pose).\nAdd a new fist controller to the selected hand to blend all fingers into a fist pose by scaling it"""

    bl_idname = "arp.add_fist_ctrl"
    bl_label = "add_fist_ctrl"
    bl_options = {'UNDO'}
    
    fist_action = None
    fist_actions_items = [] 
    choose_action = False
    current_action = None
    rig = None
    fist_type : EnumProperty(items=(('FIST', 'Fist', 'Fist pose'), ('EXTEND', 'Extend', 'Extended pose')))
    
    def get_actions_items(self, context):
        return ARP_OT_add_fist_ctrl.fist_actions_items
        
    fist_actions: EnumProperty(items=get_actions_items, default=None)
    
    @classmethod
    def poll(cls, context):
        return bpy.context.mode == 'POSE'
        
        
    def invoke(self, context, event):
        self.rig = bpy.context.active_object
        
        # something selected?
        if len(get_selected_pose_bones()) == 0:
            self.report({'ERROR'}, "The hand controller must be selected")
            return {'FINISHED'}
            
        # hand selected?
        if not "hand" in get_selected_pose_bones()[0].name:
            self.report({'ERROR'}, "The hand controller must be selected")
            return {'FINISHED'}     

        # is "rig_fist" action created?  
        self.choose_action = False
        # clear current list
        while len(self.fist_actions_items):
            self.fist_actions_items.pop(0)# make sure to use pop(idx) instead of remove(), buggy update issue
            
        for act in bpy.data.actions:
            if "rig_fist" in act.name:                
                self.fist_actions_items.append((act.name, act.name, ''))                
                
        if len(self.fist_actions_items) == 1:# use existing
            self.fist_action = self.fist_actions_items[0][0]
          
        elif len(self.fist_actions_items) > 1:# choose among existing actions
            self.choose_action = True            
            
        elif len(self.fist_actions_items) == 0:# create the fist action
            act_name = 'rig_fist'
            act = bpy.data.actions.new(act_name)
            self.fist_action = act_name
            
            # debug, need to assign new action to armature, otherwise update issue, cannot set the action constraint...
            if self.rig.animation_data:
                if self.rig.animation_data.action:
                    self.current_action = self.rig.animation_data.action# store current to restore later
            if self.rig.animation_data == None:
                self.rig.animation_data_create()
            self.rig.animation_data.action = act
            
        wm = context.window_manager
        return wm.invoke_props_dialog(self)
        #self.execute(context)
        #return {'PASS_THROUGH'}      
        
    
    def draw(self, context):
        scn = context.scene
        layout = self.layout
        if self.choose_action:
            layout.label(text='Multiple fist actions found, which one should be used?')
            layout.prop(self, 'fist_actions')
        
        layout.prop(self, 'fist_type', expand=True)
        

    def execute(self, context):
        
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        #try:
        if self.choose_action:
            self.fist_action = self.fist_actions# get the active enum
            
        _add_fist_ctrl(self.fist_action, self.fist_type)
        
        self.report({"INFO"}, "Fist controller added.")

        #finally:
        # restore action (clear if None)
        self.rig.animation_data.action = self.current_action
        
        context.preferences.edit.use_global_undo = use_global_undo
        
        mess = 'Fist pose constraints added'
        print(mess)
        self.report({'INFO'}, mess)
        return {'FINISHED'}
        
        


class ARP_OT_remove_fist_ctrl(Operator):
    """Remove the fist controller"""

    bl_idname = "arp.remove_fist_ctrl"
    bl_label = "remove_fist_ctrl"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        return bpy.context.mode == 'POSE'

    def execute(self, context):
        # something selected?
        if len(get_selected_pose_bones()) == 0:
            self.report({'ERROR'}, "Please select the hand controller first.")
            return {'FINISHED'}

            # hand selected?
        if not "hand" in get_selected_pose_bones()[0].name:
            self.report({'ERROR'}, "Please select the hand controller first.")
            return {'FINISHED'}

        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        try:
            _remove_fist_ctrl()
            self.report({"INFO"}, "Fist controller removed.")

        finally:
            context.preferences.edit.use_global_undo = use_global_undo

        return {'FINISHED'}


class ARP_OT_mirror_picker(Operator):
    """Mirror the selected picker bone(s) transforms"""

    bl_idname = "arp.mirror_picker"
    bl_label = "mirror_picker"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        if context.active_object:
            if is_object_arp(context.active_object):
                found_picker = True
                try:
                    context.scene.Proxy_Picker.active
                except:
                    found_picker = False
                if found_picker:
                    if not context.scene.Proxy_Picker.active:
                        return True

    def execute(self, context):
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        try:
            _mirror_picker()

        finally:
            context.preferences.edit.use_global_undo = use_global_undo
        return {'FINISHED'}


class ARP_OT_move_picker_layout(Operator):
    """Edit the picker layout, buttons and text position. The picker selection will be disabled.\nClick Apply Layout to complete and enable again the picker selection"""

    bl_idname = "arp.move_picker_layout"
    bl_label = "move_picker_layout"

    state: StringProperty("")

    @classmethod
    def poll(cls, context):
        if context.active_object:
            if is_object_arp(context.active_object):
                return True

    def execute(self, context):

        # Is there a picker?
        if bpy.context.active_object.data.bones.get("Picker"):

            _move_picker_layout(self.state, self)

        else:
            self.report({"ERROR"}, "Add the picker panel first.")

        return {'FINISHED'}


class ARP_OT_screenshot_head_picker(Operator):
    """Capture the current view as the facial picker background image"""

    bl_idname = "arp.screenshot_head_picker"
    bl_label = "Save .PNG"

    filepath: StringProperty(subtype="DIR_PATH", default='')

    @classmethod
    def poll(cls, context):
        if context.active_object:
            if is_object_arp(context.active_object):
                return True

    def execute(self, context):
        _screenshot_head_picker(self.filepath)
        return {'FINISHED'}

    def invoke(self, context, event):
        # Is there a picker?
        if bpy.context.active_object.data.bones.get("Picker"):
            self.filepath = 'picker_bg_face.png'
            context.window_manager.fileselect_add(self)
            return {'RUNNING_MODAL'}

        else:
            self.report({"ERROR"}, "Add the picker panel first.")
            return {'FINISHED'}


class ARP_OT_assign_colors(Operator):
    """Assign the colors"""

    bl_idname = "arp.assign_colors"
    bl_label = "assign_colors"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        if context.active_object:
            if is_object_arp(context.active_object):
                return True

    def execute(self, context):
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        try:
            _assign_colors()

        finally:
            context.preferences.edit.use_global_undo = use_global_undo
        return {'FINISHED'}


class ARP_OT_clean_skin(Operator):
    """Clean weight groups"""

    bl_idname = "arp.clean_skin"
    bl_label = "clean_skin"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        if context.active_object:
            if context.active_object.type == 'MESH':
                return True

    def execute(self, context):
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        try:
            _clean_skin()

        finally:
            context.preferences.edit.use_global_undo = use_global_undo
        return {'FINISHED'}


class ARP_OT_delete_arp(Operator):
    """Delete the selected Auto-Rig Pro armature"""

    bl_idname = "arp.delete_arp"
    bl_label = "delete_arp"
    bl_options = {'UNDO'}


    def execute(self, context):
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        try:
            _delete_arp()

        finally:
            context.preferences.edit.use_global_undo = use_global_undo
        return {'FINISHED'}


class ARP_OT_append_arp(Operator):
    """Add the Auto-Rig Pro armature in the scene"""

    bl_idname = "arp.append_arp"
    bl_label = "append_arp"
    bl_options = {'UNDO'}

    
    rig_presets_items = [('human', 'Human', "Humanoid armature"), ('dog', "Dog", "Dog armature"),
                         ('horse', 'Horse', "Horse armature"),
                         ('bird', 'Bird', "Bird armature"),
                         ('free', 'Empty', "Empty armature to add only the necessary limbs")]
                         

    def get_rig_items(self, context):
        return ARP_OT_append_arp.rig_presets_items
        

    rig_presets: EnumProperty(items=get_rig_items, default=None)
    

    def execute(self, context):
        # the separator line must be ignored
        if self.rig_presets == "____":
            return {"FINISHED"}

        """
        # check there's no ARP rig in the scene
        found = None
        for obj in bpy.data.objects:
            if obj.type == "ARMATURE":
                if obj.data.bones.get("c_pos"):
                    found = obj
                    self.report({"WARNING"}, 'Found an existing armature: "' + found.name + '"')
                    return {"FINISHED"}
        """
        """
        # if there are existing custom shapes in the scene, delete them
        cs_grp = bpy.data.objects.get("cs_grp")
        if cs_grp:
            print("Found existing customs shapes...")
            for col in cs_grp.users_collection:
                if col.name.endswith("_cs"):
                    print("  deleted collection", col.name)
                    bpy.data.collections.remove(col)

            delete_children(cs_grp, "OBJECT")
            print("  deleted custom shapes")
        """
        
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        try:
            _append_arp(self.rig_presets)
        finally:
            context.preferences.edit.use_global_undo = use_global_undo
            
        return {'FINISHED'}


class ARP_OT_apply_shape(Operator):
    """Apply the selected shape"""

    bl_idname = "arp.apply_shape"
    bl_label = "apply_shape"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        if context.active_object:
            if context.mode == 'EDIT_MESH':
                if 'cs_user' in context.active_object.name:
                    return True

    def execute(self, context):
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        try:
            _apply_shape()
        finally:
            context.preferences.edit.use_global_undo = use_global_undo
        return {'FINISHED'}


class ARP_OT_edit_custom_shape(Operator):
    """Edit the selected bone shape"""

    bl_idname = "arp.edit_custom_shape"
    bl_label = "edit_custom_shape"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        if context.mode == 'POSE':
            if bpy.context.active_pose_bone:
                return True

    def execute(self, context):
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        try:
            if bpy.context.active_pose_bone.custom_shape:
                _edit_custom_shape()
            else:
                self.report({"ERROR"}, "No custom shapes set for this bone. Create one first.")

        finally:
            context.preferences.edit.use_global_undo = use_global_undo
        return {'FINISHED'}


class ARP_OT_mirror_custom_shape(Operator):
    """Mirror the selected bone shape to the other side"""

    bl_idname = "arp.mirror_custom_shape"
    bl_label = "mirror_custom_shape"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        if context.mode == 'POSE':
            if bpy.context.active_pose_bone:
                return True

    def execute(self, context):
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        try:
            if bpy.context.active_pose_bone.custom_shape:
                _mirror_custom_shape()
            else:
                self.report({"ERROR"}, "No custom shapes set for this bone. Create one first.")

        finally:
            context.preferences.edit.use_global_undo = use_global_undo
        return {'FINISHED'}


class ARP_OT_import_colors(Operator):
    """Import the color set"""
    bl_idname = "arp.import_colors"
    bl_label = "Import Colors"

    filepath: StringProperty(subtype="FILE_PATH", default='py')

    def execute(self, context):
        _import_colors(self.filepath)
        return {'FINISHED'}

    def invoke(self, context, event):
        self.filepath = 'color_set.py'
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}


class ARP_OT_export_colors(Operator):
    """Export the color set"""
    bl_idname = "arp.export_colors"
    bl_label = "Export Colors"

    filepath: StringProperty(subtype="FILE_PATH", default='py')

    def execute(self, context):
        _export_colors(self.filepath)
        return {'FINISHED'}

    def invoke(self, context, event):
        self.filepath = 'color_set.py'
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}


class ARP_OT_export_rig_data_options(Operator):
    """Export rig data (reference bones, custom shapes)"""
    bl_idname = "arp.export_rig_data_options"
    bl_label = "Export Rig Data"

    ref_bones : BoolProperty(default=True, description="Export reference bones transforms")
    custom_shapes: BoolProperty(default=True, description="Export bones custom shapes")

    @classmethod
    def poll(cls, context):
        if context.active_object:
            return context.active_object.type == "ARMATURE"

    def draw(self, context):
        layout = self.layout
        layout.prop(self, "ref_bones", text="Reference Bones Transforms")
        layout.prop(self, "custom_shapes", text="Custom Shapes")

    def invoke(self, context, event):
        # dialog box
        wm = context.window_manager
        return wm.invoke_props_dialog(self)

    def execute(self, context):
        ARP_OT_export_rig_data.ref_bones = self.ref_bones
        ARP_OT_export_rig_data.custom_shapes = self.custom_shapes
        bpy.ops.arp.export_rig_data('INVOKE_DEFAULT')
        return {'FINISHED'}


class ARP_OT_export_rig_data(Operator):
    """Export rig data"""
    bl_idname = "arp.export_rig_data"
    bl_label = "Export Data"

    ref_bones = False
    custom_shapes = False
    filepath: StringProperty(subtype="FILE_PATH", default='py')
    filter_glob : StringProperty(default="*.py", options={'HIDDEN'})
    
    def invoke(self, context, event):
        print("export data:", self.ref_bones, self.custom_shapes)
        self.filepath = 'arp_export.py'
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}

    def execute(self, context):
        _export_rig_data(self)
        self.report({"INFO"}, "Exported")
        return {'FINISHED'}


class ARP_OT_import_rig_data_options(Operator):
    """Import rig data (reference bones, custom shapes)"""
    bl_idname = "arp.import_rig_data_options"
    bl_label = "Import Rig Data"

    import_ref_bones: BoolProperty(default=True, description="Import reference bones transforms")
    selection_only : BoolProperty(default=False, description="Import reference bones transforms for selected bones only")
    import_custom_shapes: BoolProperty(default=True, description="Import bones custom shapes")

    @classmethod
    def poll(cls, context):
        return context.active_object.type == "ARMATURE"

    def draw(self, context):
        layout = self.layout
        layout.prop(self, "import_ref_bones", text="Reference Bones Transforms")
        layout.prop(self, "selection_only", text="Selected Bones Only")
        layout.prop(self, "import_custom_shapes", text="Custom Shapes")

    def invoke(self, context, event):
        # dialog box
        wm = context.window_manager
        return wm.invoke_props_dialog(self)

    def execute(self, context):
        ARP_OT_import_rig_data.import_ref_bones = self.import_ref_bones       
        ARP_OT_import_rig_data.selection_only = self.selection_only      
        ARP_OT_import_rig_data.import_custom_shapes = self.import_custom_shapes
        
        bpy.ops.arp.import_rig_data('INVOKE_DEFAULT')
        
        return {'FINISHED'}
        
        
class ARP_OT_import_rig_data(Operator):
    """Import rig data"""
    bl_idname = "arp.import_rig_data"
    bl_label = "Import Data"

    import_ref_bones = True
    selection_only = False
    import_custom_shapes = True
    filepath: StringProperty(subtype="FILE_PATH", default='py')
    filter_glob : StringProperty(default="*.py", options={'HIDDEN'})

    @classmethod
    def poll(cls, context):
        return context.active_object.type == "ARMATURE"

    def execute(self, context):
        _import_rig_data(self)
        
        return {'FINISHED'}

    def invoke(self, context, event):
        self.filepath = 'arp_export.py'
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}


class ARP_OT_disable_limb(Operator):
    """Disable (remove safely) the selected limb"""

    bl_idname = "arp.disable_limb"
    bl_label = "disable_limb"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        if context.mode == 'EDIT_ARMATURE':
            if len(context.selected_editable_bones):
                return True
            else:
                return False


    def execute(self, context):
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        try:
            _disable_limb(self, context)

        finally:
            print("")
            context.preferences.edit.use_global_undo = use_global_undo
        return {'FINISHED'}


class ARP_OT_clean_scene(Operator):
    """Clean the current scene by removing all objects stored accidentally in the blend file, that are not used in any visible collection\nClick it if you encounter errors when rigging."""

    bl_idname = "arp.clean_scene"
    bl_label = "clean_scene"
    bl_options = {'UNDO'}

    def execute(self, context):
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        try:
            _clean_scene(self)
        finally:
            context.preferences.edit.use_global_undo = use_global_undo

        return {"FINISHED"}
        
        
class ARP_OT_check_for_update(Operator):
    """Check if a new Auto-Rig Pro version has been released"""
    
    bl_idname = "arp.check_for_update"
    bl_label = "Check for Update"        
    
    current_version = ''
    latest_version = 0
    message = ''
    
    
    def invoke(self, context, event):        
        link = "https://artell.gumroad.com/l/auto-rig-pro"
        req = requests.get(link, verify=False, timeout=5)
        f = req.text
        char1 = '--['
        char2 = ']--'
        ver_string = f[f.find(char1)+3 : f.find(char2)]
        if ver_string == '':
            self.message = 'Failed to check, is there an internet connection?'
        else:
            ver_list = ver_string.split('.')
            ver_int = int(ver_list[0] + ver_list[1] + ver_list[2])
            self.latest_version = ver_int        
            self.current_version = get_autorigpro_version()
            if self.current_version < self.latest_version:
                latest_ver_string = ver_int_to_str(self.latest_version)                
                self.message = 'New version available ' + latest_ver_string
            else:
                current_ver_format = str(self.current_version)[0]+'.'+\
                                    str(self.current_version)[1]+str(self.current_version)[2]+'.'+\
                                    str(self.current_version)[3]+str(self.current_version)[4]
                self.message = 'Already up to date! ['+current_ver_format+'] Keep on riggin\''
            
        
        # show window
        wm = context.window_manager
        return wm.invoke_props_dialog(self, width=400)
        

    def draw(self, context):
        layout = self.layout
        layout.label(text=self.message)
        

    def execute(self, context): 
        print("Checked update")
        return {'FINISHED'}
    

class ARP_OT_update_armature(Operator):
    """Update old armatures to the latest version\nMay require to click 'Match to Rig' afterward to correct bones alignment"""

    bl_idname = "arp.update_armature"
    bl_label = "Update Armature"
    bl_options = {'UNDO'}

    required: BoolProperty(default=False)
    breaking: BoolProperty(default=False)    
    update_axes_consistent: BoolProperty(default=False, description='Make foot and arms Z axis up for consistency')
    show_breaking_updates = []
    updated_features = []

    @classmethod
    def poll(cls, context):
        if context.active_object:
            if is_object_arp(context.active_object):
                return True

    def invoke(self, context, event):
        rig = context.active_object
        
        # init props
        if get_data_bone("c_jawbone.x"):
            if get_data_bone("jawbone.x") == None or get_data_bone("eyelid_top.l") == None:
                self.breaking = True
                self.required = True

        self.updated_features = []
        self.update_axes_consistent = False
        self.show_breaking_updates = []
        
        if 'arp_updated' in rig.data.keys():
            update_int = int(rig.data['arp_updated'].replace('.', ''))
            if update_int < 36319:
                self.show_breaking_updates.append('consistent_axes')

        # show window
        wm = context.window_manager
        return wm.invoke_props_dialog(self, width=400)

    def draw(self, context):
        layout = self.layout
        if self.breaking:
            layout.label(text="Important Note!", icon='INFO')
            layout.label(text="This update includes transforms changes for the following bone controllers:")
            layout.label(text="Eyelids, Jawbone")
            layout.label(text="Therefore it will break these controllers existing poses or animations.")
            layout.label(text="Continue?")
         
        else:
            layout.label(text="The selected armature will be updated to Auto-Rig Pro latest requirements.")
            layout.separator()
            
        if len(self.show_breaking_updates):
            layout.label(text='The following updates can break existing poses and animations, enable ')
            layout.label(text='them only if necessary:')
            if 'consistent_axes' in self.show_breaking_updates:
                layout.prop(self, 'update_axes_consistent', text='Feet and Arms Z-Up ')                
            layout.separator()

    def execute(self, context):
        
        if bpy.app.version < (3,2,0):            
            if is_proxy(context.active_object):
                self.report({'ERROR'}, "Armature cannot be updated in proxy mode")
                return {'FINISHED'}

        _update_armature(self, context, required=self.required)

        # Report info message
        mess = ""

        if len(self.updated_features):
            mess = "Updated without errors! The following changes were applied:"
            for i in self.updated_features:
                mess += '\n* '+i
            
            mess += '\n'
            mess += '\n'
            mess += "Click Match to Rig to fix bones rotations if necessary<icon>INFO"   
            mess += '\n'
        else:
            mess = "No changes, already up to date! Keep on riggin'."
            
        
        bpy.ops.arp.report_message('INVOKE_DEFAULT', message=mess, icon_type='INFO')

        return {'FINISHED'}


class ARP_OT_set_shape_key_driver(Operator):
    """Add a keyframe point on the selected shape key driver curve (0 or 1) according the bone transform value"""

    bl_idname = "arp.set_shape_key_driver"
    bl_label = "set_shape_key_driver"
    bl_options = {'UNDO'}

    value: StringProperty(name="Driver Value")

    @classmethod
    def poll(cls, context):
        return (context.active_object)

    def execute(self, context):
        if context.active_object.type != 'MESH':
            self.report({'ERROR'}, "Select the mesh and the shape key")
            return {'FINISHED'}

        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        try:
            _set_shape_key_driver(self, self.value)

        finally:
            context.preferences.edit.use_global_undo = use_global_undo
        return {'FINISHED'}


class ARP_OT_pick_bone(Operator):
    """Get the selected bone"""

    bl_idname = "arp.pick_bone"
    bl_label = "pick_bone"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        return (context.active_object)

    def execute(self, context):
        if context.active_object.type != 'ARMATURE':
            self.report({'ERROR'}, "First select a bone to pick it.")
            return {'FINISHED'}

        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        try:
            _pick_bone()

        finally:
            context.preferences.edit.use_global_undo = use_global_undo
        return {'FINISHED'}


class ARP_OT_create_driver(Operator):
    """Create a driver for the selected shape key using the Bone name and Bone transform parameter. Select first the armature then the mesh object"""

    bl_idname = "arp.create_driver"
    bl_label = "create_driver"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        return (context.active_object)

    def execute(self, context):
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False
        try:
            _create_driver()

        finally:
            context.preferences.edit.use_global_undo = use_global_undo
        return {'FINISHED'}


class ARP_OT_set_picker_camera(Operator):
    """Display the bone picker in this active view"""

    bl_idname = "arp.set_picker_camera"
    bl_label = "set_picker_camera"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        if context.active_object:
            if is_object_arp(context.active_object):
                return True

    def execute(self, context):
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False
        try:

            rig_functions._set_picker_camera(self)

        finally:
            context.preferences.edit.use_global_undo = use_global_undo
        return {'FINISHED'}


class ARP_OT_bind_VHDS(Operator):
    """Bind with the Voxel Heat Diffuse Skinning addon"""
    bl_idname = "arp.bind_vhds"
    bl_label = "Run VHDS"

    _timer = None
    obj_to_skin = []
    obj_to_skin_save = []
    rig_add = None
    rig_add_skin_started = False
    sides = [".l", ".r"]
    rig = None
    rig_original = None
    modal_state = "execute_1"
    bind_rig = True
    bind_rig_add = True
    enable_head_refine = True
    smooth_twists = True
    auto_eyeballs = True
    chin_loc = None
    improve_hips_skinning = True
    improve_heels_skinning = True

    ebones_dict = {}
    simplify_value = None
    simplify_subd = None
    active_obj_name = ""
    selected_obj = []
    sel_verts = {}
    eyelids_count = 0
    error_mess = ''
    
    @classmethod
    def poll(cls, context):
        if context.active_object:
            if context.active_object.type == "ARMATURE":
                return True

    def modal(self, context, event):
        scn = bpy.context.scene
        
        try:
            if self.modal_state == "execute_1":
                print("Prepare skinning...")
                bind_prepare(self)

                print("Run Voxel Heat Diffuse Skinning...")
                scn.voxel_protect = scn.arp_bind_sel_verts
                bpy.ops.wm.voxel_heat_diffuse()

                self.modal_state = "wait_for_skin"

                return {'PASS_THROUGH'}

            elif self.modal_state == "wait_for_skin":
                if event.type == 'TIMER':
                    if scn.voxel_job_finished:
                        # bind to rig_add if necessary
                        if self.rig.arp_secondary_type == "ADDITIVE" and self.rig_add_skin_started == False:
                            if self.rig_add:
                                bpy.ops.object.mode_set(mode='OBJECT')
                                bpy.ops.object.select_all(action='DESELECT')

                                for obj_name in self.obj_to_skin:
                                    set_active_object(obj_name)

                                set_active_object(self.rig_add.name)

                                print("Run Voxel Heat Diffuse Skinning (rig_add)...")
                                bpy.ops.wm.voxel_heat_diffuse()

                                self.rig_add_skin_started = True
                                self.modal_state = "wait_for_skin"

                                return {'PASS_THROUGH'}

                        else:
                            self.modal_state = "execute_2"

                            # eyeball skinning
                            for obj_name in self.obj_to_skin:
                                obj = get_object(obj_name)

                                if self.auto_eyeballs:
                                    ret = bind_skin_eyeballs(obj, self)
                                    if ret == "continue":
                                        continue

                return {'PASS_THROUGH'}

            elif self.modal_state == "execute_2":
                print("Improve weights...")

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

                for obj_name in self.obj_to_skin:
                    bpy.ops.object.mode_set(mode='OBJECT')
                    obj = get_object(obj_name)
                    set_active_object(obj_name)

                    # setup armature modifiers settings
                    add_armature_modifiers(self, remove_current=True)

                    bind_improve_weights(obj, self)

                bind_set_collec(self)
                bind_finalize(self)
                bind_parent(self)

                self.modal_state = "restore"


        except Exception as e:
            self.error_mess = 'Error: '+str(e)
            self.modal_state = "restore"
            return {'PASS_THROUGH'}

        if self.modal_state == "restore":
            print("Restore rig data...")
            # restore bones data
            set_active_object(self.active_obj_name)
            
            bpy.ops.object.mode_set(mode='EDIT')

            restore_bones_data = True
            if restore_bones_data:
                restore_rig_data(self)

            bpy.ops.object.mode_set(mode='OBJECT')
            
            # restore selected verts
            if scn.arp_bind_sel_verts:                
                for o_name in self.sel_verts:
                    o = get_object(o_name)
                    set_active_object(o_name)
                    vert_list = self.sel_verts[o_name]
                    
                    for vi in vert_list:
                        o.data.vertices[vi].select = True
                            

            # restore selection
            for o_name in self.selected_obj:
                o = get_object(o_name)
                set_active_object(o_name)
            
            set_active_object(self.active_obj_name)

            # restore simplify
            restore_simplify(self)
            
            if self.error_mess != '':
                bpy.ops.arp.report_message('INVOKE_DEFAULT', message=self.error_mess, icon_type='ERROR')
                
            return {'FINISHED'}


    def execute(self, context):
        scn = context.scene
        
        error_mess = ''
        
        # is a mesh selected?
        found_mesh = False

        for o in context.selected_objects:
            if o.type == "MESH":
                found_mesh = True
                break

        if not found_mesh:
            self.report({'ERROR'}, "Select at least a mesh and the armature")
            return {'FINISHED'}

        try:# check if the prop is there
            context.scene.voxel_job_finished
        except:
            self.report({'ERROR'}, "Update to the latest version of Voxel Heat Diffuse Skinning required")
            return {'FINISHED'}
            
        # save selected verts
        if scn.arp_bind_sel_verts:
            for o in context.selected_objects:
                if o.type == 'MESH':
                    vert_list = [v.index for v in o.data.vertices if v.select]
                    self.sel_verts[o.name] = vert_list

        # save edit bones data to restore if any error
        save_ebone_data(self)

        rig_name = bpy.context.active_object.name

        # get the limbs
        limb_sides.get_multi_limbs()

        simplify_scene(self)

        # save selection
        self.active_obj_name = context.active_object.name
        self.selected_obj = [i.name for i in bpy.context.selected_objects]

        # make sure to unbind first
        _unbind_to_rig()
        set_active_object(self.active_obj_name)

        wm = context.window_manager
        self._timer = wm.event_timer_add(0.5, window=context.window)
        wm.modal_handler_add(self)

        # init vars
        self.modal_state = "execute_1"
        self.rig_add_skin_started = False

        return {'RUNNING_MODAL'}

    def cancel(self, context):
        wm = context.window_manager
        wm.event_timer_remove(self._timer)


class ARP_OT_bind_to_rig(Operator):
    """Bind selected meshes to rig.\nSelect first the meshes, then the armature"""

    bl_idname = "arp.bind_to_rig"
    bl_label = "bind_to_rig"
    bl_options = {'UNDO'}

    binding_error: BoolProperty(default=False)
    binding_time: FloatProperty(default=0.0)

    obj_to_skin = []
    obj_to_skin_save = []
    rig_add = None
    sides = [".l", ".r"]
    rig = None 
    rig_original = None
    phase = "prepare"
    bind_rig = True
    bind_rig_add = True
    enable_head_refine = True
    smooth_twists = True
    auto_eyeballs = True
    chin_loc = None
    improve_hips_skinning = True
    improve_heels_skinning = True

    scale_fixed_objects = []
    ebones_dict = {}
    simplify_value = None
    simplify_subd = None
    scale_ratio = 20    
    
    show_armature_mods_warning = False
    show_high_poly_warning = False    
    
    @classmethod
    def poll(cls, context):
        if context.active_object:
            if context.active_object.type == "ARMATURE":
                return True

    
    def invoke(self, context, event):     
        prefs = bpy.context.preferences.addons[__package__].preferences
        
        for obj in bpy.context.selected_objects:
            if obj.type == "MESH":
                # high poly warning
                if len(obj.data.polygons) > 150000:
                    self.show_high_poly_warning = True
                    # Open dialog
                    wm = context.window_manager
                    return wm.invoke_props_dialog(self, width=400)
                
                # armature modifiers found warning                
                if prefs.rem_arm_mods_set == False:
                    for mod in obj.modifiers:
                        if mod.type == 'ARMATURE':
                            self.show_armature_mods_warning = True
                            # Open dialog
                            wm = context.window_manager
                            return wm.invoke_props_dialog(self, width=400)
                    
        self.execute(context)

        return {'PASS_THROUGH'}
        
        
    def draw(self, context):
        
        layout = self.layout
        if self.show_armature_mods_warning:
            prefs = bpy.context.preferences.addons[__package__].preferences
            layout.label(text="Meshes already contain armature modifiers, remove them before binding?", icon='INFO')
            layout.label(text='This setting will be saved, can be changed later in the addon preferences')
            layout.prop(prefs, 'remove_existing_arm_mods')
            

        if self.show_high_poly_warning:
            layout.label(text="More than 75.000 polygons to bind, can take a while. Continue?", icon='INFO')


    def execute(self, context):
        prefs = bpy.context.preferences.addons[__package__].preferences
        # apply remove armature modifiers first time default
        if self.show_armature_mods_warning:
            prefs.rem_arm_mods_set = True
        
        # is a mesh selected?
        found_mesh = False

        for o in bpy.context.selected_objects:
            if o.type == "MESH":
                found_mesh = True
                break

        if not found_mesh:
            self.report({'ERROR'}, "Select at least a mesh and the armature")
            return {'FINISHED'}
            
        # disable mirror       
        self.rig = bpy.context.active_object
        xmirror_state = self.rig.data.use_mirror_x
        self.rig.data.use_mirror_x = False

        # save edit bones data to restore if any error
        save_ebone_data(self)

        rig_name = bpy.context.active_object.name

        # get the limbs
        limb_sides.get_multi_limbs()

        # simplify for performances reasons
        simplify_scene(self)

        # Undo
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False
        
        # save selection
        active_obj_name = context.active_object.name
        selected_obj = [i.name for i in bpy.context.selected_objects]     
        
        debug = False
        error_mess = ''
        
        if debug:# debug mode, hard break and reports error 
            _unbind_to_rig()            
            set_active_object(active_obj_name)
            _bind_to_rig(self, context)
            return {'FINISHED'}
            
        else:# release mode, handles error internally smoothly and recover
            try: 
                _unbind_to_rig()            
                set_active_object(active_obj_name)
                _bind_to_rig(self, context)

            except Exception as e:
                error_mess = 'Error: ' + str(e)
            
            finally:         
                print("Restore rig data...")
                # restore bones data
                set_active_object(rig_name)
                self.rig.data.pose_position = 'POSE'
                
                if self.rig_original == None:# do not/cannot restore bones data of linked armatures
                    bpy.ops.object.mode_set(mode='EDIT')

                    restore_bones_data = True
                    if restore_bones_data:
                        restore_rig_data(self)

                bpy.ops.object.mode_set(mode='OBJECT')

                # delete voxelized object
                voxelized_object = get_object("arp_full_character_voxelized")
                if voxelized_object:
                    delete_object(voxelized_object)

                # restore scale fixed objects
                restore_scale_fix(self)

                # hide the rig_add
                rig_add = get_rig_add(get_object(rig_name))
                if rig_add:
                    rig_add.select_set(state=False)
                    hide_object(rig_add)

                # restore selection
                for i in selected_obj:
                    set_active_object(i)
                set_active_object(active_obj_name)

                # restore undo
                context.preferences.edit.use_global_undo = use_global_undo
                
                # restore mirror            
                self.rig.data.use_mirror_x = xmirror_state

                # restore simplify
                restore_simplify(self)
                
                if error_mess != '':
                    bpy.ops.arp.report_message('INVOKE_DEFAULT', message=error_mess, icon_type='ERROR')
                    
                self.report({'INFO'}, "Bound in " + str(round(self.binding_time, 1)) + ' seconds')
            
            return {'FINISHED'}


class ARP_OT_unbind_to_rig(Operator):
    """Unbind the selected meshes from the rig"""

    bl_idname = "arp.unbind_to_rig"
    bl_label = "unbind_to_rig"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        if context.active_object:
            if context.active_object.type == 'MESH':
                return True

    def execute(self, context):
        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False
        try:
            _unbind_to_rig(full_unbind=True)

        finally:
            context.preferences.edit.use_global_undo = use_global_undo
        return {'FINISHED'}


class ARP_OT_edit_ref(Operator):
    """Display and edit the reference bones"""

    bl_idname = "arp.edit_ref"
    bl_label = "edit_ref"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        if context.active_object:
            if is_object_arp(context.active_object):
                if not context.active_object.data.layers[17]:
                    return True

    def execute(self, context):
        try:  # check if the armature is selected
            get_bones = bpy.context.active_object.data.bones
        except AttributeError:
            self.report({'ERROR'}, "Select the rig object")
            return {'FINISHED'}
            
        # check if it's a linked rig
        if bpy.context.active_object.override_library:
            self.report({'ERROR'}, "Linked rigs can't be edited")
            return {'FINISHED'}

        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False
        try:
            _edit_ref()

        finally:
            context.preferences.edit.use_global_undo = use_global_undo
            pass
            
        return {'FINISHED'}


class ARP_MT_add_limb_menu(Menu):
    bl_label = "Add Limbs Specials"

    def draw(self, _context):
        layout = self.layout
        layout.operator("arp.save_limb", text="Save Selected Bones as Custom Limb")
        layout.operator("arp.remove_custom_limbs", text="Remove Custom Limb...")


class CustomLimbsToDel:
    list = {}
    
    def update(self):
        self.list = {}        
        for cl in ARP_OT_add_limb.limbs: 
            if cl[0].endswith('_customlimb'):
                cl_name = cl[0].replace('_customlimb', '')
                self.list[cl_name] = False
                
    def remove_entry(self, name):        
        self.list.pop(name)        
    
    def __init__(self):
        #self.update()
        return
                
custom_limbs_todel = CustomLimbsToDel()

        
class ARP_OT_remove_custom_limbs(Operator):
    """Remove Custom limbs..."""
    
    bl_idname = "arp.remove_custom_limbs"
    bl_label = "Remove Custom Limbs"
    
    def invoke(self, context, event):
        update_limbs_presets()
        custom_limbs_todel.update()
        # Open dialog
        wm = context.window_manager
        return wm.invoke_props_dialog(self, width=400)
        
    def draw(self, context):
        layout = self.layout
        
        if len(custom_limbs_todel.list):
            for cl in custom_limbs_todel.list:
                row = layout.row(align=True)
                row.label(text=cl)
                icon_name = 'CHECKBOX_HLT' if custom_limbs_todel.list[cl] else 'CHECKBOX_DEHLT'             
                row.operator('arp.exec_rem_custom_limbs', text='', icon='X').name = cl
        else:
            layout.label(text='No Custom Limbs added yet!')

    def execute(self, context):
        return {'FINISHED'}
        
        
class ARP_OT_exec_rem_custom_limbs(Operator):
    """Remove the custom limb"""
    
    bl_idname = "arp.exec_rem_custom_limbs"
    bl_label = "Remove"
    
    name: StringProperty(default='')
    
    def execute(self, context):
        print('Deleting', self.name, '...')
        limbs_directory = bpy.context.preferences.addons[__package__].preferences.custom_limb_path
        if not (limbs_directory.endswith("\\") or limbs_directory.endswith('/')):
            limbs_directory += '/'      
        try:
            os.listdir(limbs_directory)
        except:            
            return

        for file_name in os.listdir(limbs_directory):
            filepath = os.path.join(limbs_directory,file_name)            
            os.remove(filepath)
            print('removed', filepath)            
            break
            
        custom_limbs_todel.remove_entry(self.name)
        update_limbs_presets()
        
        return {'FINISHED'}
        
        
class ARP_OT_save_limb(Operator):
    """ Save the selected bones into a new limb preset """

    bl_idname = "arp.save_limb"
    bl_label = "Save Limb"

    limb_name: StringProperty(default="")
    arp_bone_selected: StringProperty(default="")

    def draw(self, context):
        layout = self.layout
        if self.arp_bone_selected != "":
            layout.label(text="Warning: ARP limb bone selected: " + self.arp_bone_selected, icon='ERROR')
            layout.label(text="Only custom bones are fully supported for now, not ARP limbs")
        layout.prop(self, "limb_name", text="Limb Name")

    def invoke(self, context, event):
        # check if Auto-Rig Pro limbs are selected (not yet supported, only user created bones should be selected)
        excluded_list = ["facial_markers", "bones_arp_layer", "bone_update_locations"]
        self.arp_bone_selected = ""

        current_mod = get_current_mode()

        bpy.ops.object.mode_set(mode='EDIT')

        # anything selected?
        if len(get_selected_edit_bones()) == 0:
            self.report({"ERROR"}, "Select bones to save first")
            return {'FINISHED'}

        side = get_bone_side(get_selected_edit_bones()[0].name)

        for i in dir(ard):
            if self.arp_bone_selected != "":
                break
            if i in excluded_list:
                continue
            bones_list = getattr(ard, i)
            if type(bones_list) != list:
                continue

            for edit_b in get_selected_edit_bones():
                if self.arp_bone_selected != "":
                    break
                for base_name in bones_list:
                    if get_bone_base_name(edit_b.name) in base_name:
                        self.arp_bone_selected = edit_b.name
                        break

        restore_current_mode(current_mod)

        # Open dialog
        wm = context.window_manager
        return wm.invoke_props_dialog(self, width=400)

    def execute(self, context):
        if self.limb_name == "":
            self.report({"ERROR"}, "Enter a name for this new limb")
            return {"FINISHED"}
        try:
            limbs_path = bpy.context.preferences.addons[__package__].preferences.custom_limb_path

            bpy.ops.object.mode_set(mode='EDIT')

            rig_name = bpy.context.active_object.name
            rig = bpy.data.objects[rig_name]
            xmirror_state = rig.data.use_mirror_x
            rig.data.use_mirror_x = False

            # fetch edit bones data
            bones_data_edit = edit_bones_data_to_dict(get_selected_edit_bones())
            bpy.ops.object.mode_set(mode='POSE')

            # fetch pose bones data
            bones_data_pose, bone_groups_list = pose_bones_data_to_dict(get_selected_pose_bones())
            # fetch constraints data
            bones_data_cns = pose_bones_constraints_to_dict(rig, get_selected_pose_bones())
            # fetch custom shapes data
            custom_shapes_data = pose_bones_custom_shapes_to_dict(get_selected_pose_bones())
            # fetch bone groups data
            bone_group_data = bones_groups_to_dict(rig, bone_groups_list)
            # fetch drivers data
            drivers_data = drivers_to_dict(rig, get_selected_pose_bones())

            # save file
            # add extension
            if not (limbs_path.endswith("\\") or limbs_path.endswith('/')):
                limbs_path += '/'

            file_path = limbs_path + self.limb_name + ".py"
            if not os.path.exists(os.path.dirname(file_path)):
                try:
                    os.makedirs(os.path.dirname(file_path))
                except:
                    pass

                    # write
            file = open(file_path, "w", encoding="utf8", newline="\n")
            file.write(str(bones_data_edit) + "\n")
            file.write(str(bones_data_pose) + "\n")
            file.write(str(bones_data_cns) + "\n")
            file.write(str(custom_shapes_data) + "\n")
            file.write(str(bone_group_data) + "\n")
            file.write(str(drivers_data))
            file.close()

            # update the list
            update_limbs_presets()

            # Restore mirror
            rig.data.use_mirror_x = xmirror_state

        finally:
            print("Limb Saved")
        return {'FINISHED'}


def update_limbs_presets():
    # print("  look for custom limbs...")
    limbs_directory = bpy.context.preferences.addons[__package__].preferences.custom_limb_path
    if not (limbs_directory.endswith("\\") or limbs_directory.endswith('/')):
        limbs_directory += '/'

    add_separator = True

    try:
        os.listdir(limbs_directory)
    except:
        #print("The custom limb directory seems invalid:", limbs_directory)
        return
        
    # reset
    for i, cl in enumerate(ARP_OT_add_limb.limbs):
        if cl[0].endswith('_customlimb'):
            ARP_OT_add_limb.limbs.pop(i)
    
    # set
    for file in os.listdir(limbs_directory):
        if not file.endswith(".py"):
            continue
        preset_name = file.replace('.py', '')

        already_in_list = False
        for i in ARP_OT_add_limb.limbs:
            if i[0] == preset_name + "_customlimb":
                already_in_list = True
            if i[0] == "____":
                add_separator = False

        if already_in_list:
            continue

        # add a line as visual separator in the list, to differentiate user added presets
        if add_separator:
            ARP_OT_add_limb.limbs.append(('____', '____Custom____', '____'))

        ARP_OT_add_limb.limbs.append((preset_name + "_customlimb", preset_name, preset_name))


class ARP_OT_add_limb(Operator):
    """Add a limb"""

    bl_idname = "arp.add_limb"
    bl_label = "add_limb"
    bl_options = {'UNDO'}

    limbs = [
        ('spine', 'Spine', "FK spine, from 1 to 4 bones"), ('arm.l', 'Arm (Left)', ""), ('arm.r', 'Arm (Right)', ""),
        ('leg.l', "Leg (Left)", ""),
        ('leg.r', "Leg (Right)", ""), ('head', 'Head', ""), ('ears', 'Ears', ''), ('tail', 'Tail', ""),
        ('breast', 'Breast', ''), ('spline_ik', 'Chain: Spline IK', ''), ('bbones', 'Chain: Bendy Bones', '')]

    def get_limbs_items(self, context):
        return ARP_OT_add_limb.limbs

    limbs_presets: EnumProperty(items=get_limbs_items, default=None)

    report_message = ""

    def execute(self, context):
        # the separator line must be ignored
        if self.limbs_presets == "____":
            return {"FINISHED"}

        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        try:
            _add_limb(self, self.limbs_presets)
            if not self.limbs_presets.endswith(
                    "_customlimb"):  # only display the reference bones layer for built-in limbs (requires Match to Rig)
                _edit_ref(deselect=False)

        finally:
            if self.report_message != "":        
                bpy.ops.arp.report_message('INVOKE_DEFAULT', message=self.report_message, icon_type='INFO')

            context.preferences.edit.use_global_undo = use_global_undo
        return {'FINISHED'}


class ARP_OT_dupli_limb(Operator):
    """ Duplicate the selected limb"""

    bl_idname = "arp.dupli_limb"
    bl_label = "dupli_limb"
    bl_options = {'UNDO'}
    
    @classmethod
    def poll(cls, context):
        if context.mode == 'EDIT_ARMATURE':
            if len(context.selected_editable_bones) > 0:
                bone = context.selected_editable_bones[0]
                if len(bone.keys()) > 0:
                    if 'arp_duplicate' in bone.keys():
                        return True

    def execute(self, context):
        try:  # check if the armature is selected
            get_bones = bpy.context.active_object.data.bones
        except AttributeError:
            self.report({'ERROR'}, "Select the rig object")
            return {'FINISHED'}

        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False
        try:
            _dupli_limb()
        finally:
            context.preferences.edit.use_global_undo = use_global_undo
        return {'FINISHED'}
        
        
class ARP_OT_dupli_limb_mirror(Operator):
    """ Duplicate and mirror the selected limb to the opposite side"""

    bl_idname = "arp.dupli_limb_mirror"
    bl_label = "dupli_limb_mirror"
    bl_options = {'UNDO'}
    
    @classmethod
    def poll(cls, context):
        if context.mode == 'EDIT_ARMATURE':
            if len(context.selected_editable_bones) > 0:
                bone = context.selected_editable_bones[0]
                if len(bone.keys()) > 0:
                    if 'arp_duplicate' in bone.keys():
                        return True

    def execute(self, context):
        # Checks
        eb = bpy.context.selected_editable_bones[0]
        #   is it a middle limb?
        if not eb.name.endswith('.l') and not eb.name.endswith('.r'):
            self.report({'ERROR'}, "Middle limb (.x), can't be mirrored")
            return {'FINISHED'}
    
        # Run
        try:  # check if the armature is selected
            get_bones = bpy.context.active_object.data.bones
        except AttributeError:
            self.report({'ERROR'}, "Select the rig object")
            return {'FINISHED'}

        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False
        try:
            _dupli_limb(dupli_mirror=True)
        finally:
            context.preferences.edit.use_global_undo = use_global_undo
        return {'FINISHED'}


class Limb_Sides:
    arm_sides = [".l", ".r"]
    leg_sides = [".l", ".r"]
    head_sides = [".x"]
    ear_sides = [".l", ".r"]
    tail_sides = ['.x']
    spine_sides = [".x"]
    wing_sides = []
    spline_sides = []
    bbones_sides = []

    def init_values(self):
        self.arm_sides = []
        self.leg_sides = []
        self.head_sides = []
        self.ear_sides = []
        self.tail_sides = []
        self.spine_sides = []       
        self.wing_sides = []
        self.spline_sides = []
        self.bbones_sides = []

    def get_multi_limbs(self):
        arm = bpy.context.active_object

        # reset values
        self.init_values()
        
        for bone in arm.data.bones:# proxy armature can't enter edit mode, iterate on bones data instead of edit bones
            if not bone.layers[17]:#only reference bones
                continue

            # Spines
            if bone.name.startswith("root_ref."):
                if not bone.name[-2:] in self.spine_sides:
                    self.spine_sides.append(bone.name[-2:])

            if bone.name.startswith("root_ref_dupli"):
                if not bone.name[-12:] in self.spine_sides:
                    self.spine_sides.append(bone.name[-12:])

            # Arms
            if bone.name.startswith("shoulder_ref."):
                if not bone.name[-2:] in self.arm_sides:
                    self.arm_sides.append(bone.name[-2:])

            if bone.name.startswith("shoulder_ref_dupli"):
                if not bone.name[-12:] in self.arm_sides:
                    self.arm_sides.append(bone.name[-12:])

            # Legs
            if bone.name.startswith("thigh_ref."):
                if not bone.name[-2:] in self.leg_sides:
                    self.leg_sides.append(bone.name[-2:])

            if bone.name.startswith("thigh_ref_dupli"):
                if not bone.name[-12:] in self.leg_sides:
                    self.leg_sides.append(bone.name[-12:])

            # Heads
            if bone.name.startswith("neck_ref."):
                if not bone.name[-2:] in self.head_sides:
                    self.head_sides.append(bone.name[-2:])

            if bone.name.startswith("neck_ref_dupli"):
                if not bone.name[-12:] in self.head_sides:
                    self.head_sides.append(bone.name[-12:])

            # Ears
            if bone.name.startswith("ear_01_ref."):
                if not bone.name[-2:] in self.ear_sides:
                    self.ear_sides.append(bone.name[-2:])

            if bone.name.startswith("ear_01_ref_dupli_"):
                if not bone.name[-12:] in self.ear_sides:
                    self.ear_sides.append(bone.name[-12:])
            
            # Tails
            if bone.name.startswith("tail_00_ref."):
                if not bone.name[-2:] in self.tail_sides:
                    self.tail_sides.append(bone.name[-2:])

            if bone.name.startswith("tail_00_ref_dupli_"):
                if not bone.name[-12:] in self.tail_sides:
                    self.tail_sides.append(bone.name[-12:])
            
            
            # Wings
            if bone.name.startswith("arm_feather_01_01_ref."):
                if not bone.name[-2:] in self.wing_sides:
                    self.wing_sides.append(bone.name[-2:])

            if bone.name.startswith("arm_feather_01_01_ref_dupli_"):
                if not bone.name[-12:] in self.wing_sides:
                    self.wing_sides.append(bone.name[-12:])

            # Splines IK
            if bone.name.startswith("spline_01_ref.") or ("arp_spline" in bone.keys() and not "_ref_dupli_" in bone.name):
                if not bone.name[-2:] in self.spline_sides:
                    self.spline_sides.append(bone.name[-2:])

            if bone.name.startswith("spline_01_ref_dupli_") or ("arp_spline" in bone.keys() and "_dupli" in bone.name):
                if not bone.name[-12:] in self.spline_sides:# -12 = '_dupli_001.x'
                    self.spline_sides.append(bone.name[-12:])
                    
            # Bbones
            if bone.name.startswith("bbones_01_ref.") or "arp_bbones" in bone.keys():
                if not bone.name[-2:] in self.bbones_sides:
                    self.bbones_sides.append(bone.name[-2:])

            if bone.name.startswith("bbones_01_ref_dupli_") or ("arp_bbones" in bone.keys() and "_dupli_" in bone.name):
                if not bone.name[-12:] in self.bbones_sides:
                    self.bbones_sides.append(bone.name[-12:])


limb_sides = Limb_Sides()


class ARP_OT_match_to_rig(Operator):
    """Generate the final rig from the reference bones"""

    bl_idname = "arp.match_to_rig"
    bl_label = "match_to_rig"
    bl_options = {'UNDO'}

    state_proxy_picker: BoolProperty(default=False)
    state_xmirror: BoolProperty(default=False)

    @classmethod
    def poll(cls, context):
        if context.active_object:
            if is_object_arp(context.active_object):
                return True

    def execute(self, context):
        try:
            get_bones = bpy.context.active_object.data.bones
        except:
            self.report({'ERROR'}, "Select the rig object")
            return {'FINISHED'}

        if bpy.context.active_object.data.bones.get("c_head_scale_fix.x"):
            self.report({'ERROR'}, "Armature not up to date. Click Update Armature in the Misc tab.")
            return {'FINISHED'}
            
        # check if it's a linked rig
        if bpy.context.active_object.override_library:
            self.report({'ERROR'}, "Linked rigs can't be edited")
            return {'FINISHED'}

        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        # Disable subsurf for faster performances
        simplify_value = bpy.context.scene.render.use_simplify
        simplify_subd = bpy.context.scene.render.simplify_subdivision
        bpy.context.scene.render.use_simplify = True
        bpy.context.scene.render.simplify_subdivision = 0

        try:
            rig_name = context.active_object.name
            rig_add = get_rig_add(bpy.data.objects[rig_name])

            # Generate additive rig if secondary controllers are set to Additive
            if context.active_object.arp_secondary_type == "ADDITIVE":
                if rig_add == None:
                    print("Rig add not found, generate it")
                    rig_add = refresh_rig_add(bpy.data.objects[rig_name])
                    copy_bones_to_rig_add(bpy.data.objects[rig_name], rig_add)
            else:
                # else, delete additive rig
                if rig_add:
                    bpy.data.objects.remove(rig_add, do_unlink=True)
                    rig_add = None

            if context.scene.arp_init_scale:
                # Initialize armatures scale
                # Apply armature scale only if not already initialized (can lead to bones roll issues otherwise)
                go_initialize_scale = False
                if rig_add:
                    if rig_add.scale != Vector((1.0, 1.0, 1.0)):
                        go_initialize_scale = True

                if bpy.data.objects[rig_name].scale != Vector((1.0, 1.0, 1.0)):
                    go_initialize_scale = True

                if go_initialize_scale:
                    init_arp_scale(rig_name, rig_add=rig_add)
                else:
                    print("Armature scale already initialized")

            _initialize_armature(self)

            # Multi limb support
            limb_sides.get_multi_limbs()

            # Align bones
            _set_masters()
            _align_arm_limbs()
            _align_leg_limbs()
            _align_spine_limbs()
            _align_wing_limbs()
            _align_spline_limbs()
            _align_bendy_limbs()
            _set_transform_constraints()            
            _reset_stretches()
            _set_inverse()
            _finalize_armature(self)

            # Set pose position
            bpy.ops.object.mode_set(mode='POSE')
            bpy.context.active_object.data.pose_position = 'POSE'

            bpy.context.active_object.show_in_front = False

            self.report({'INFO'}, "Rig Done")

        finally:
            context.preferences.edit.use_global_undo = use_global_undo
            # restore simplify
            bpy.context.scene.render.use_simplify = simplify_value
            bpy.context.scene.render.simplify_subdivision = simplify_subd

        return {'FINISHED'}


class ARP_OT_align_wings(Operator):
    """Align wing bones"""

    bl_idname = "arp.align_wings"
    bl_label = "align_wings"
    bl_options = {'UNDO'}

    """
    @classmethod
    def poll(cls, context):
        if context.active_object:
            if is_object_arp(context.active_object):
                return True
    """

    def execute(self, context):
        try:
            get_bones = bpy.context.active_object.data.bones
        except:
            self.report({'ERROR'}, "Select the rig object")
            return {'FINISHED'}

        use_global_undo = context.preferences.edit.use_global_undo
        context.preferences.edit.use_global_undo = False

        try:
            # Multi limb support
            limb_sides.get_multi_limbs()
            _align_wing_limbs()
            _reset_stretches()

        finally:
            context.preferences.edit.use_global_undo = use_global_undo

        return {'FINISHED'}


##########################  FUNCTIONS  ##########################


## UTILS FUNCTIONS

def get_first_master_controller():
    if bpy.context.active_object.arp_master_fly:
        return ("c_fly")
    else:
        return ("c_traj")


def is_proxy_bone(bone):
    # bone = edit bone or pose bone

    if bone.parent:
        bone_parent1 = bone.parent.name
    else:
        bone_parent1 = "None"

    if '_proxy' in bone.name or 'Picker' in bone_parent1 or bone.name == "Picker":
        return True


def save_pose():
    # save the controllers current transforms and properties
    # returns a dict: controller:[loc, rot, scale], [properties]
    dict = {}
    for pbone in bpy.context.active_object.pose.bones:
        if not pbone.name.startswith("c_"):
            continue
        rot = pbone.rotation_euler.copy() if pbone.rotation_mode != "QUATERNION" else pbone.rotation_quaternion.copy()
        prop_dict = {}
        for prop_name in pbone.keys():
            if prop_name != "_RNA_UI":
                prop_dict[prop_name] = pbone.get(prop_name)

        dict[pbone.name] = [pbone.location.copy(), rot, pbone.scale.copy()], prop_dict

    return dict


def restore_pose(dict):
    # restore the controllers transforms and properties
    # from a dict
    for pbone_name in dict:
        pbone = get_pose_bone(pbone_name)
        pbone.location = dict[pbone_name][0][0]
        if pbone.rotation_mode != "QUATERNION":
            pbone.rotation_euler[0] = dict[pbone_name][0][1][0]
            pbone.rotation_euler[1] = dict[pbone_name][0][1][1]
            pbone.rotation_euler[2] = dict[pbone_name][0][1][2]
            #print("RESTORE ROT", pbone.rotation_euler)
        else:
            pbone.rotation_quaternion = dict[pbone_name][0][1]
        pbone.scale = dict[pbone_name][0][2]

        prop_dict = dict[pbone_name][1]
        for prop_name in prop_dict:
            pbone[prop_name] = prop_dict[prop_name]


def custom_props_to_dict(dict=None, bone=None):
    if len(bone.keys()) > 0:
        for k in bone.keys():
            if k == "_RNA_UI":
                continue
            if type(bone[k]) not in [str, int, float]:  # only export simple type variable for now
                continue
            min, max = -1000, 1000
           
            try:
                min, max = get_prop_setting(bone, k, 'min'), get_prop_setting(bone, k, 'max')
            except:
                pass

            dict[k] = bone[k], min, max

    return dict


def edit_bones_data_to_dict(edit_bones_list):
    # returns a dict of edit bones data, string compatible
    bones_data = {}
    exclude_edit_props = ['__doc__', '__module__', '__slots__', 'bl_rna', 'double', 'matrix', 'rna_type', 'transform',
                          'parent', 'align_orientation', 'align_roll', 'layers', 'children']
    valid_prop_type = [float, int, str, list, bool]

    for ebone in edit_bones_list:
        ebone_props = {}
        for prop in dir(ebone):
            if prop in exclude_edit_props:
                continue
            try:
                getattr(ebone, prop)
            except:
                continue

            # convert Vector to list
            prop_val = getattr(ebone, prop)
            if type(getattr(ebone, prop)) == Vector:
                prop_val = [prop_val[0], prop_val[1], prop_val[2]]

            if type(prop_val) not in valid_prop_type:
                print("Could not save edit bone property:", prop_val)
                prop_val = ""

            ebone_props[prop] = prop_val

        # we need all props string-compatible for export:
        # parent edit bone name
        parent_name = ""
        if ebone.parent:
            parent_name = ebone.parent.name
        ebone_props["parent"] = parent_name

        # layers
        ebone_props["layers"] = [i for i in ebone.layers]

        # custom props
        ebone_custom_props_dict = {}
        ebone_props["custom_props"] = custom_props_to_dict(dict=ebone_custom_props_dict, bone=ebone)

        bones_data[ebone.name] = ebone_props

    return bones_data


def pose_bones_data_to_dict(pose_bones_list):
    # returns a dict of pose bones data
    # and a list of bones groups names
    bone_groups_list = []
    bones_data = {}
    pbone_prop_list = ['bbone_curveinx', get_bbone_param_name('bbone_curveinz'), 'bbone_curveoutx', get_bbone_param_name('bbone_curveoutz'),
                       'bbone_custom_handle_end', 'bbone_custom_handle_start', 'bbone_easein', 'bbone_easeout',
                       'bbone_rollin', 'bbone_rollout', 'bbone_scaleinx', 'bbone_scaleiny', 'bbone_scaleoutx',
                       'bbone_scaleouty', 'custom_shape_scale', 'custom_shape_scale_xyz', 'ik_max_x', 'ik_max_y', 'ik_max_z', 'ik_min_x',
                       'ik_min_y', 'ik_min_z', 'ik_rotation_weight', 'ik_stiffness_x', 'ik_stiffness_y',
                       'ik_stiffness_z', 'ik_stretch', 'location', 'lock_ik_x', 'lock_ik_y', 'lock_ik_z',
                       'lock_rotation_w', 'lock_rotations_4d', 'rotation_mode', 'scale', 'use_custom_shape_bone_size',
                       'use_ik_limit_x', 'use_ik_limit_y', 'use_ik_limit_z', 'use_ik_linear_control',
                       'use_ik_rotation_control']# custom_shape_scale is deprecated in post 3.0, only there for backward-compatibility
    valid_prop_type = [float, int, str, list, bool]

    for pbone in pose_bones_list:
        pbone_props = {}
        for prop in pbone_prop_list:
            try:
                getattr(pbone, prop)
            except:
                continue

            # convert Vector to list
            prop_val = getattr(pbone, prop)
            if type(getattr(pbone, prop)) == Vector:
                prop_val = [prop_val[0], prop_val[1], prop_val[2]]

            if type(prop_val) not in valid_prop_type:
                print("Could not save pose bone property:", prop_val)
                prop_val = ""

            pbone_props[prop] = prop_val

        # lock location, rotation, scale
        pbone_props["lock_location"] = [i for i in pbone.lock_location]
        pbone_props["lock_rotation"] = [i for i in pbone.lock_rotation]
        pbone_props["lock_scale"] = [i for i in pbone.lock_scale]

        # rotation
        pbone_props["rotation_axis_angle"] = [i for i in pbone.rotation_axis_angle]
        pbone_props["rotation_euler"] = [i for i in pbone.rotation_euler]
        pbone_props["rotation_quaternion"] = [i for i in pbone.rotation_quaternion]

        # custom prop
        pbone_custom_props_dict = {}
        pbone_props["custom_props"] = custom_props_to_dict(dict=pbone_custom_props_dict, bone=pbone)

        # bone group
        if pbone.bone_group:
            group_name = pbone.bone_group.name
            pbone_props["bone_group"] = group_name
            if not group_name in bone_groups_list:
                bone_groups_list.append(group_name)

        bones_data[pbone.name] = pbone_props

    return bones_data, bone_groups_list


def pose_bones_constraints_to_dict(armature_object, pose_bones_list):
    # returns a dict of bones constraints, containing a dict of constraints data
    # bones_data[bone_name] = constraint_data[constraint_name]
    bones_data = {}
    exclude_cns_props = ['__doc__', '__module__', '__slots__', 'active', 'bl_rna', 'error_location', 'error_rotation',
                         'is_proxy_local', 'is_valid', 'rna_type', 'joint_bindings']

    def get_constraint_relative_target(target):
        if target == armature_object:
            return "rig__self"
        else:
            return target.name

    for pbone in pose_bones_list:
        if len(pbone.constraints) == 0:
            continue
        cns_dict_list = []
        for cns in pbone.constraints:
            cns_data = {}
            for prop in dir(cns):
                if prop in exclude_cns_props or "matrix" in prop:  # no need to export matrices (Child Of constraints)
                    continue

                if prop == "action":
                    if cns.action:
                        cns_data["action"] = cns.action.name
                        continue

                # get the name of the target object instead of pointer to be string compatible
                if prop == "target":
                    if cns.target:
                        # save the rig as special variable since its name can change, to import it properly later
                        cns_data["target"] = get_constraint_relative_target(cns.target)
                        continue

                # armature constraints have multiple targets
                if prop == "targets":
                    targets_list = []
                    for tar in cns.targets:
                        if tar == None:
                            targets_list.append(["", "", tar.weight])
                            continue
                        tar_name = get_constraint_relative_target(tar.target)
                        targets_list.append([tar_name, tar.subtarget, tar.weight])
                    cns_data["targets"] = targets_list
                    continue

                if prop == "pole_target":
                    if cns.pole_target:
                        cns_data["pole_target"] = get_constraint_relative_target(cns.pole_target)
                    continue

                try:
                    getattr(cns, prop)
                except:
                    continue

                prop_val = getattr(cns, prop)

                # convert Vector to list
                if type(prop_val) == Vector:
                    prop_val = [prop_val[0], prop_val[1], prop_val[2]]
                # convert Object to string, object name
                if type(prop_val) == bpy.types.Object:
                    prop_val = get_constraint_relative_target(prop_val)

                cns_data[prop] = prop_val

            cns_dict_list.append(cns_data)

        bones_data[pbone.name] = cns_dict_list

    return bones_data


def pose_bones_custom_shapes_to_dict(pose_bones_list):
    # returns a dict of custom shape data for each pose bone
    shapes_data = {}
    for pbone in pose_bones_list:
        if pbone.custom_shape:
            shape_name = pbone.custom_shape.name
            # export mesh data
            cs_mesh = pbone.custom_shape.data
            verts = [(v.co[0], v.co[1], v.co[2]) for v in cs_mesh.vertices]
            edges = [(edge.vertices[0], edge.vertices[1]) for edge in cs_mesh.edges]
            faces = []
            for face in cs_mesh.polygons:
                face_verts = []
                for v in face.vertices:
                    face_verts.append(v)
                faces.append(face_verts)

            shapes_data[pbone.name] = shape_name, verts, edges, faces, get_custom_shape_scale(pbone, uniform=False, as_list=True)
            

    return shapes_data


def bones_groups_to_dict(armature_object, bone_groups_list):
    # returns a dict of bones group data (bones colors group)
    group_data = {}
    for group_name in bone_groups_list:
        bgroup = armature_object.pose.bone_groups[group_name]
        normal_color = [i for i in bgroup.colors.normal]
        select_color = [i for i in bgroup.colors.select]
        active_color = [i for i in bgroup.colors.active]
        group_data[group_name] = normal_color, select_color, active_color

    return group_data


def drivers_to_dict(armature, pbone_list):
    # return a dict of drivers data, containing other dicts for variables, targets...
    # e.g. drivers_data['pose.bones["....']] = driver_props['array_index']
    drivers_data = {}
    drivers_armature = None
    if armature.animation_data != None:
        drivers_armature = armature.animation_data.drivers

    for pbone in pbone_list:
        if drivers_armature == None:
            continue
        for dr in drivers_armature:
            pbone_datapath = 'pose.bones["' + pbone.name + '"]'
            if dr.data_path.startswith(pbone_datapath):
                driver_props = {}

                # driver fcurves data
                fc_keyf_data = [get_keyf_data(key) for key in dr.keyframe_points]
                driver_props["fcurve"] = fc_keyf_data
                driver_props["array_index"] = dr.array_index

                # driver data
                driver_props["type"] = dr.driver.type
                driver_props["expression"] = dr.driver.expression
                driver_props["use_self"] = dr.driver.use_self

                # driver variables data
                driver_vars = {}
                for var in dr.driver.variables:
                    driver_var_props = {}
                    # type
                    driver_var_props["type"] = var.type
                    # targets
                    targets_list = []
                    for tar in var.targets:
                        targets_data = {}
                        id = "None"
                        if tar.id:
                            if tar.id == armature:  # reference to self armature to be imported properly later
                                id = "rig__self"
                            else:
                                id = tar.id.name
                        targets_data["id"] = id
                        targets_data["bone_target"] = tar.bone_target
                        targets_data["data_path"] = tar.data_path
                        targets_data["id_type"] = tar.id_type
                        targets_data["rotation_mode"] = tar.rotation_mode
                        targets_data["transform_space"] = tar.transform_space
                        targets_data["transform_type"] = tar.transform_type
                        targets_list.append(targets_data)

                    driver_var_props["targets"] = targets_list

                    driver_vars[var.name] = driver_var_props

                driver_props["variables"] = driver_vars
                drivers_data[dr.data_path+'|'+str(dr.array_index)] = driver_props
    #print("DRIVERS DATA", drivers_data)
    return drivers_data


def create_bones_from_data(armature=None, edit_data=None, pose_data=None, cns_data=None, shape_data=None,
                           groups_data=None, drivers_data=None):
    dupli_bones_dict = {}# in case of name clashing, create a dict of this form: {original_bone_name: created_bone_name}, e.g. {"Bone": "Bone.001"}

    def get_target_bone_name(bone_name):
        # returns the bone name either part of the new limb (may have been renamed if duplicate of existing bone)
        # or outside of the new limb
        if bone_name in dupli_bones_dict:
            return dupli_bones_dict[bone_name]
        else:
            return bone_name

    # edit mode data

    limb_layers = [lay for lay in armature.data.layers]
    for bone_name in edit_data:
        ebone = armature.data.edit_bones.new(bone_name)
        prop_dict = edit_data[bone_name]
        for prop in prop_dict:
            # custom properties
            if prop == "custom_props":
                cprop_dict = prop_dict[prop]
                for cprop_name in cprop_dict:
                    create_custom_prop(node=ebone, prop_name=cprop_name, prop_val=cprop_dict[cprop_name][0],
                                       prop_min=cprop_dict[cprop_name][1], prop_max=cprop_dict[cprop_name][2])
                continue

            # collect layers to display them
            if prop == "layers":
                for i, j in enumerate(prop_dict["layers"]):
                    if limb_layers[
                        i] == False and j == True and i != 31:  # the deforming bone layer 31 is not really necessary?
                        limb_layers[i] = True

            # others
            try:
                setattr(ebone, prop, prop_dict[prop])
            except:
                pass
        dupli_bones_dict[bone_name] = ebone.name

    # parents must be set in a second loop, after adding bones
    for bone_name in edit_data:
        final_bone_name = dupli_bones_dict[bone_name]
        ebone = get_edit_bone(final_bone_name)
        prop_dict = edit_data[bone_name]
        if prop_dict["parent"] == None:
            continue
        parent_name = prop_dict["parent"]
        final_parent_name = get_target_bone_name(parent_name)
        ebone.parent = get_edit_bone(final_parent_name)

    bpy.ops.object.mode_set(mode='POSE')

    # bone groups color
    for group_name in groups_data:
        group = armature.pose.bone_groups.get(group_name)
        if group == None:  # the group doesn't exist yet, create it
            group = armature.pose.bone_groups.new(name=group_name)
            normal_color = groups_data[group_name][0]
            select_color = groups_data[group_name][1]
            active_color = groups_data[group_name][2]
            group.colors.normal = normal_color
            group.colors.select = select_color
            group.colors.active = active_color
            group.color_set = "CUSTOM"

    # pose mode data
    for bone_name in pose_data:
        final_bone_name = dupli_bones_dict[bone_name]
        pbone = get_pose_bone(final_bone_name)
        prop_dict = pose_data[bone_name]
        for prop in prop_dict:
            # bone groups
            if prop == "bone_group":
                group_name = prop_dict[prop]
                pbone.bone_group = armature.pose.bone_groups.get(group_name)
            # custom properties
            if prop == "custom_props":
                cprop_dict = prop_dict[prop]
                for cprop_name in cprop_dict:
                    create_custom_prop(node=pbone, prop_name=cprop_name, prop_val=cprop_dict[cprop_name][0],
                                       prop_min=cprop_dict[cprop_name][1], prop_max=cprop_dict[cprop_name][2])
                continue
            # others
            try:
                setattr(pbone, prop, prop_dict[prop])
            except:
                pass

    def get_constraint_target(target_name):
        # returns the constraint target object, being the current rig or other object
        if target_name == None:
            return None
        if target_name == "rig__self":
            return armature
        else:
            return bpy.data.objects.get(target_name)

    # constraints data
    for bone_name in cns_data:
        final_bone_name = dupli_bones_dict[bone_name]
        pbone = get_pose_bone(final_bone_name)
        for cns_dict in cns_data[bone_name]:
            new_cns = pbone.constraints.new(cns_dict["type"])
            for cns_prop in cns_dict:
                # specials
                if cns_prop == "action":
                    action_name = cns_dict[cns_prop]
                    setattr(new_cns, cns_prop, bpy.data.actions.get(action_name))

                elif cns_prop == "type":  # type can only be set when creating the constraint before
                    continue

                elif cns_prop in ["target", "pole_target", "space_object"]:  # fetch the object from name
                    target_name = cns_dict[cns_prop]
                    setattr(new_cns, cns_prop, get_constraint_target(target_name))
                    continue

                elif cns_prop == "targets":  # armature constraints have multiple targets
                    for tar in cns_dict[cns_prop]:
                        tar_obj_name, tar_bone_name, tar_weight = tar[0], tar[1], tar[2]
                        t = new_cns.targets.new()
                        t.target = get_constraint_target(tar_obj_name)
                        t.subtarget = get_target_bone_name(tar_bone_name)
                        t.weight = tar_weight
                    continue

                elif "subtarget" in cns_prop:
                    setattr(new_cns, cns_prop, get_target_bone_name(cns_dict[cns_prop]))
                    continue

                # common props
                try:
                    setattr(new_cns, cns_prop, cns_dict[cns_prop])
                except:
                    pass

            # set Child Of constraints inverse matrix
            if new_cns.type == "CHILD_OF":
                set_constraint_inverse_matrix(new_cns)

    # custom shape data
    for bone_name in shape_data:
        shape_name = shape_data[bone_name][0]
        shape = get_object(shape_name)
        if shape == None:  # the shape doesn't exist in the file yet
            # create it
            verts, edges, faces = shape_data[bone_name][1], shape_data[bone_name][2], shape_data[bone_name][3]
            shape = create_object_mesh(shape_name, verts, edges, faces)

            # set in collection
            col_rig = get_rig_collection(armature)
            col_master = get_master_collection(col_rig)
            cs_collec = get_cs_collection(col_master)
          
            if cs_collec == None:
                cs_collec = bpy.data.collections.new("cs_grp")
                bpy.context.collection.children.link(cs_collec)
                
            cs_collec.objects.link(shape)
            
            # hide it
            hide_object(shape)

        # set the custom shape
        final_bone_name = dupli_bones_dict[bone_name]
        pbone = get_pose_bone(final_bone_name)
        pbone.custom_shape = shape
        if len(shape_data[bone_name]) >= 5:#backward-compatibility
            shape_scale = shape_data[bone_name][4]            
            set_custom_shape_scale(pbone, shape_scale)

    # drivers data
    create_drivers_from_dict(drivers_data, obj=armature, dupli_bones_dict=dupli_bones_dict)

    # display limb layers
    for i, lay in enumerate(limb_layers):
        armature.data.layers[i] = lay


def create_drivers_from_dict(dict, obj=None, dupli_bones_dict=None, key_interpolation=None):
    
    if obj == None:
        obj=bpy.context.active_object

    if obj.animation_data == None:
        obj.animation_data_create()
    drivers_list = obj.animation_data.drivers

    def get_target_bone_name(bone_name):
        # returns the bone name either part of the new limb (may have been renamed if duplicate of existing bone)
        # or outside of the new limb
        if dupli_bones_dict:
            if bone_name in dupli_bones_dict:
                return dupli_bones_dict[bone_name]

        return bone_name

    for dp_id in dict:
        dp = None
        if len(dp_id.split('|')) == 1:#backward-compatibility
            dp = dp_id
        else:
            dp = dp_id.split('|')[0]

        driver_props = dict[dp_id]
        array_idx = driver_props["array_index"]
        bone_name = dp.split('"')[1]

        dp_final = dp
        if dupli_bones_dict:
            final_bone_name = dupli_bones_dict[bone_name]
            dp_final = dp.replace(bone_name, final_bone_name)

        # look for existing one before creating a new one
        if drivers_list.find(dp_final, index=array_idx) == None:           
        
            dr = obj.animation_data.drivers.new(data_path=dp_final, index=array_idx)

            # driver fcurves data
            # remove all default keyframe, then set new keyframes
            clear_fcurve(dr)
            for keyf_data in driver_props["fcurve"]:
                new_key = dr.keyframe_points.insert(keyf_data[0], keyf_data[1])
                set_keyf_data(new_key, keyf_data)
                if key_interpolation:
                    new_key.interpolation = key_interpolation

            # driver data
            dr.driver.type = driver_props["type"]
            dr.driver.expression = driver_props["expression"]
            dr.driver.use_self = driver_props["use_self"]

            # driver variables data
            driver_vars = driver_props["variables"]
            for var_name in driver_vars:
                driver_var_props = driver_vars[var_name]
                var = dr.driver.variables.new()
                var.name = var_name
                # type
                var.type = driver_var_props["type"]
                # targets
                targets_list = driver_var_props["targets"]
                tar_idx = 0
                for tar_data in targets_list:
                    tar = var.targets[tar_idx]
                    # tar.id_type = tar_data["id_type"]
                    id_name = tar_data["id"]
                    if id_name == "rig__self":
                        id_name = obj.name
                    id_type_string = tar_data["id_type"].lower() + 's'# e.g. OBJECT > objects
                    tar.id = getattr(bpy.data, id_type_string).get(id_name)
                    target_bone_name = tar_data["bone_target"]
                    tar.bone_target = get_target_bone_name(target_bone_name)

                    tar_data_path = tar_data["data_path"]
                    if tar_data_path.startswith("pose.bones"):# replace the data path with the final bone name instead
                        tar_b_name = tar_data_path.split('"')[1]
                        if dupli_bones_dict:
                            if tar_b_name in dupli_bones_dict:
                                tar_b_name_final = dupli_bones_dict[tar_b_name]
                                tar_data_path = tar_data_path.replace(tar_b_name, tar_b_name_final)
                            
                    tar.data_path = tar_data_path

                    tar.rotation_mode = tar_data["rotation_mode"]
                    tar.transform_space = tar_data["transform_space"]
                    tar.transform_type = tar_data["transform_type"]

                    tar_idx += 1

        if bpy.app.version >= (3,0,0):
            convert_drivers_cs_to_xyz(obj)
                
def find_edge_with_vert(edges_list, given_vert, exclude_list):
    # returns a list of edges containing the given vert
    found = []
    for edge in edges_list:
        if edge in exclude_list:
            #print("Edge", edge.index, "is in exclude list, continue...")
            continue
        for vert in edge.verts:
            if vert == given_vert:
                #print("Edge", edge.index, "is found")
                found.append(edge)

    return found


## OPERATOR FUNCTIONS #####################################################################
def _pick_object(prop):
    if prop == "eyeball":
        bpy.context.scene.arp_eyeball_name = bpy.context.active_object.name
    elif prop == "eyeball_right":
        bpy.context.scene.arp_eyeball_name_right = bpy.context.active_object.name


def _set_eyelids_borders(self):
    if self.action == "Clear":
        eyelids_borders_data.left_borders = eyelids_borders_data.right_borders = None
        return

    head_obj = bpy.context.active_object
    mesh = bmesh.from_edit_mesh(head_obj.data)
    verts_coords = []# [[vertex_index, (vertex cos)], [2, (0.2,0.5,0.3)]...]
    debug_print = False
    # collect vertices coordinates
        # an edge loop has been selected with automatic selection, no vertice data in select_history
        # build the edge loop order
    if len(mesh.select_history) <= 1:
        selected_edges = [e for e in mesh.edges if e.select]
        edges_loop_list = [selected_edges[0]]
        find_loop = True
        last_edge = selected_edges[0]
        last_vert = selected_edges[0].verts[0]

        if debug_print:
            print("Find loop...")
            for e in edges_loop_list:
                indices = [v.index for v in e.verts]
                print(indices)
            print("")

        while find_loop:
            found = find_edge_with_vert(selected_edges, last_vert, edges_loop_list)
            if debug_print:
                print("Found", len(found), "edges", found)
            # valid edge found
            if len(found) == 1:
                edges_loop_list.append(found[0])
                last_edge = found[0]
                last_vert = found[0].other_vert(last_vert)
            else:
                #print("Not a loop")
                find_loop = False

            if debug_print:
                print("constructing loop:")
                for e in edges_loop_list:
                    indices = [v.index for v in e.verts]
                    print(indices)

                if len(selected_edges) == len(edges_loop_list):
                    if debug_print:
                        print("Loop completed!")
                    find_loop = False

        for e_loop in edges_loop_list:
            for v in e_loop.verts:
                verts_coords.append([v.index, head_obj.matrix_world @ v.co])
        self.report({"INFO"}, "Auto Loop Set")

    else:
        # else, vertices have been selected manually one by one
        for v in mesh.select_history:
            if v.select:
                verts_coords.append([v.index, head_obj.matrix_world @ v.co])
        self.report({"INFO"}, "Manual Loop Set")


    # store data
    if self.action == "Set Left":
        eyelids_borders_data.left_borders = verts_coords
    elif self.action == "Set Right":
        eyelids_borders_data.right_borders = verts_coords

    #print("left", eyelids_borders_data.left_borders, "right", eyelids_borders_data.right_borders)


def get_spline_name(side):
    # returns the spline name for the current side
    name = "spline"# default name, backward-compatibility
    rig = bpy.context.active_object

    for b in rig.data.bones:
        if b.keys():
            bside = get_bone_side(b.name)
            if bside == side:
                if "arp_spline" in b.keys() and "_ref" in b.name:
                    #name = b.name.split('_')[0]
                    #name_split = b.name.split('_')
                    #id = name_split[len(name_split)-1].split('.')[0]
                    #name = spline_name+'_'+id_1_str+'_ref'+spline_side# "spline_01_ref" + parent_name[11:]
                    name = b['arp_spline']
                    break

    return name


def _align_spline_limbs():
    # disable X mirror
    xmirror_state = bpy.context.object.data.use_mirror_x
    bpy.context.object.data.use_mirror_x = False
    bpy.context.scene.tool_settings.use_keyframe_insert_auto = False

    if len(limb_sides.spline_sides) > 0:
        print("\n Align Spline IKs...")

    # -- Pose Mode--
    bpy.ops.object.mode_set(mode='POSE')

    # reset pose
        # store active pose
    bpy.ops.pose.select_all(action='SELECT')
    controllers_saved_transforms = save_pose()
        # reset
    auto_rig_reset.reset_all()

    for side in limb_sides.spline_sides:
        print("  [", side, "]")

        # -- Edit Mode
        bpy.ops.object.mode_set(mode='EDIT')

        name = get_spline_name(side)

        root_ref_bone = get_edit_bone(name + "_01_ref" + side)
        amount = root_ref_bone["spline_count"]
        type = "1"

        if "spline_type" in root_ref_bone.keys():#backward-compatibility
            type = root_ref_bone["spline_type"]
        cont_freq = 1
        if "spline_cont_freq" in root_ref_bone.keys():
            cont_freq = root_ref_bone["spline_cont_freq"]
        smoothness = 4
        if "spline_smoothness" in root_ref_bone.keys():
            smoothness = root_ref_bone["spline_smoothness"]
        spline_masters_data = None
        if "spline_masters_data" in root_ref_bone.keys():
            spline_masters_data = dict_to_int(root_ref_bone["spline_masters_data"])
        spline_inters_data = None
        if "spline_inters_data" in root_ref_bone.keys():
            spline_inters_data = dict_to_int(root_ref_bone["spline_inters_data"])
        interpolation = 'LINEAR'
        if "spline_interpolation" in root_ref_bone.keys():
            interpolation = root_ref_bone["spline_interpolation"]

        ref_bones_dict = {}
        for i in range(1, amount + 1):
            id = '%02d' % (i)
            bname = name + "_" + id + "_ref" + side
            bref = get_edit_bone(bname)
            ref_bones_dict[bname] = bref.head.copy(), bref.tail.copy(), bref.roll

        # align bones
        align_spline_ik_bones(name, side)

        bpy.ops.object.mode_set(mode='POSE')
        reset_spline_stretch_ctrl(name, side)

        # --Object Mode--
        bpy.ops.object.mode_set(mode='OBJECT')
        rig_name = bpy.context.active_object.name
        arp_armature = bpy.data.objects.get(rig_name)

        # set the NurbsCurve
        nurbs = create_spline_nurbs(_amount=amount, _arp_armature=arp_armature, _side_arg=side, _smoothness=smoothness)

        # align points to bones
        new_spline = nurbs.data.splines[0]
        align_spline_curve(new_spline, ref_bones_dict)

        # add hook modifiers to controllers
        set_spline_hooks(spline=nurbs, armature=arp_armature, length=amount, freq=cont_freq, interpolation=interpolation, type=type, spline_masters_data=spline_masters_data, spline_inters_data=spline_inters_data, side=side, name=name)
        nurbs.parent = arp_armature
        hide_object(nurbs)

        set_active_object(arp_armature.name)

        # set spline IK constraint target
        bpy.ops.object.mode_set(mode='POSE')
        id = '%02d' % (amount)
        last_bone_name = name + "_" + id + side
        last_pbone = get_pose_bone(last_bone_name)
        splineik_cns = last_pbone.constraints.get("Spline IK")
        if splineik_cns:
            splineik_cns.target = bpy.data.objects.get(nurbs.name)


    # restore pose
    # -- Pose Mode--
    bpy.ops.object.mode_set(mode='POSE')
    restore_pose(controllers_saved_transforms)

    # -- Edit Mode--
    bpy.ops.object.mode_set(mode='EDIT')

    # restore X mirror
    bpy.context.object.data.use_mirror_x = xmirror_state

    if len(limb_sides.spline_sides) > 0:
        print("")


def _align_bendy_limbs():
    # disable X mirror
    xmirror_state = bpy.context.object.data.use_mirror_x
    bpy.context.object.data.use_mirror_x = False

    bpy.context.scene.tool_settings.use_keyframe_insert_auto = False

    if len(limb_sides.bbones_sides) > 0:
        print("\n Align Bendy Bones...")

    # -- Pose Mode--
    bpy.ops.object.mode_set(mode='POSE')

    for side in limb_sides.bbones_sides:
        print("[",side,"]")
        # -- Edit Mode
        bpy.ops.object.mode_set(mode='EDIT')
        name = get_bbones_name(side)
        align_bendy_bones(name, side)

    # restore X mirror
    bpy.context.object.data.use_mirror_x = xmirror_state

    if len(limb_sides.spline_sides) > 0:
        print("")


def _align_wing_limbs():
    # disable X mirror
    xmirror_state = bpy.context.object.data.use_mirror_x
    bpy.context.object.data.use_mirror_x = False

    if len(limb_sides.wing_sides) > 0:
        print("\nAlign Wings...")

    for side in limb_sides.wing_sides:
        print("  side", side)
        bpy.ops.object.mode_set(mode='EDIT')

        # get the feathers count from custom props
        arm_ref = get_edit_bone("arm_ref" + side)
        arm_feathers_count = arm_ref["arp_feathers"]

        wings_enabled = arm_ref["arp_wings"]
        feathers_layers = arm_ref["arp_feathers_layers"]
        feathers_subdiv = arm_ref["arp_feathers_subdiv"]
        feathers_fold_controller = arm_ref["arp_feathers_fold_controller"]

        forearm_ref = get_edit_bone("forearm_ref" + side)
        forearm_feathers_count = forearm_ref["arp_feathers"]

        hand_ref = get_edit_bone("hand_ref" + side)
        hand_feathers_count = hand_ref["arp_feathers"]

        # Collect ref bones
        # arm
        arm_f_ref_bones = []

        for i in range(1, 32):
            index = "{0:0=2d}".format(i)

            for j in range(1, feathers_layers + 1):
                layeridx = "{0:0=2d}".format(j)
                bname = "arm_feather_" + index + "_" + layeridx + "_ref" + side
                arm_ref_f = get_edit_bone(bname)

                if arm_ref_f:
                    arm_f_ref_bones.append(bname)

                    # forearm
        forearm_f_ref_bones = []
        for i in range(1, 32):
            index = "{0:0=2d}".format(i)

            for j in range(1, feathers_layers + 1):
                layeridx = "{0:0=2d}".format(j)
                bname = "forearm_feather_" + index + "_" + layeridx + "_ref" + side
                forearm_ref_f = get_edit_bone(bname)

                if forearm_ref_f:
                    forearm_f_ref_bones.append(bname)

                    # hand
        hand_f_ref_bones = []
        for i in range(1, 32):
            index = "{0:0=2d}".format(i)

            for j in range(1, feathers_layers + 1):
                layeridx = "{0:0=2d}".format(j)
                bname = "hand_feather_" + index + "_" + layeridx + "_ref" + side
                hand_ref_f = get_edit_bone(bname)

                if hand_ref_f:
                    hand_f_ref_bones.append(bname)

                    # Main feathers
        align_feather_main(arm_feathers_count, forearm_feathers_count, hand_feathers_count, feathers_layers, side)

        # Mids
        first_arm_feather = get_edit_bone(arm_f_ref_bones[feathers_layers - 1])
        last_arm_feather = get_edit_bone(arm_f_ref_bones[len(arm_f_ref_bones) - 1])
        first_forearm_feather = get_edit_bone(forearm_f_ref_bones[feathers_layers - 1])
        last_forearm_feather = get_edit_bone(forearm_f_ref_bones[len(forearm_f_ref_bones) - 1])
        first_hand_feather = get_edit_bone(hand_f_ref_bones[feathers_layers - 1])
        last_hand_feather = get_edit_bone(hand_f_ref_bones[len(hand_f_ref_bones) - 1])

        align_feather_mid(first_arm_feather, last_arm_feather, first_forearm_feather, last_forearm_feather,
                          first_hand_feather, side)

        # Feather_stretches bones
        arm_feather_stretch = get_edit_bone("arm_feather_stretch" + side)
        forearm_feather_stretch = get_edit_bone("forearm_feather_stretch" + side)
        hand_feather_stretch = get_edit_bone("hand_feather_stretch" + side)

        align_feather_stretches(last_hand_feather, side)

        # Targets
        align_feather_targets(arm_feathers_count, forearm_feathers_count, hand_feathers_count, side)

        # Mid_targets
        align_feather_mid_targets(side)

        # Controllers
        align_feather_controls(arm_feathers_count, forearm_feathers_count, hand_feathers_count, feathers_layers,
                               feathers_subdiv, side)

        # Hand feather master
        hand_feather_master_name = "c_hand_feather_master" + side
        align_feather_hand_master(hand_feather_master_name, last_hand_feather)

        # Wings fold

        if feathers_fold_controller:
            # align
            fold_ref_name = "wings_fold_ref" + side
            fold_ref = get_edit_bone(fold_ref_name)
            fold_cont_name = "c_wings_fold" + side
            fold_cont = get_edit_bone(fold_cont_name)
            if fold_ref and fold_cont:
                copy_bone_transforms(fold_ref, fold_cont)

            # get the action
            wings_action = None
            for act in bpy.data.actions:
                if "rig_wings_fold" in act.name:
                    wings_action = act
                    break

            # get all feathers controllers
            bpy.ops.object.mode_set(mode='POSE')
            arm_controllers = ["c_shoulder" + side, "c_arm_fk" + side, "c_forearm_fk" + side, "c_hand_fk" + side]
            feather_controllers = get_feather_controllers(side)

            # set constraints
            if wings_action:
                fold_cont_pbone = get_pose_bone(fold_cont_name)

                for fc_name in feather_controllers + arm_controllers:
                    fc = get_pose_bone(fc_name)

                    action_cns = None
                    if len(fc.constraints) > 0:
                        action_cns = fc.constraints.get("Action")

                    if action_cns == None:
                        print("Create constraint")
                        action_cns = fc.constraints.new("ACTION")
                        action_cns.name = "Action"
                        # move up the constraint
                        bpy.context.active_object.data.bones.active = fc.bone
                        my_context = bpy.context.copy()
                        my_context["constraint"] = action_cns
                        for i in range(0, len(fc.constraints)):
                            bpy.ops.constraint.move_up(my_context, constraint=action_cns.name, owner='BONE')

                        action_cns.action = wings_action
                        action_cns.transform_channel = "SCALE_Y"
                        action_cns.target_space = "LOCAL"
                        action_cns.min = 1.0
                        action_cns.max = 0.5
                        action_cns.frame_start = 0
                        action_cns.frame_end = 10

                    action_cns.target = bpy.context.active_object
                    action_cns.subtarget = "c_wings_fold" + side
                    print("set subtarget", "c_wings_fold" + side)


            else:
                print('No "rig_wings_fold" action found')

        else:
            # get all feathers controllers
            bpy.ops.object.mode_set(mode='POSE')
            arm_controllers = ["c_shoulder" + side, "c_arm_fk" + side, "c_forearm_fk" + side, "c_hand_fk" + side]
            feather_controllers = get_feather_controllers(side)

            # remove constraints
            for fc_name in feather_controllers + arm_controllers:
                fc = get_pose_bone(fc_name)
                if len(fc.constraints) > 0:
                    action_cns = fc.constraints.get("Action")
                    if action_cns:
                        fc.constraints.remove(action_cns)

    # restore X mirror
    bpy.context.object.data.use_mirror_x = xmirror_state


# end _align_wing_limbs()

def get_feather_controllers(side):
    list = []
    for pbone in bpy.context.active_object.pose.bones:
        bside = get_bone_side(pbone.name)
        if side != bside:
            continue

        if pbone.name.startswith("c_hand_feather") or pbone.name.startswith(
                "c_forearm_feather") or pbone.name.startswith("c_arm_feather"):
            list.append(pbone.name)

    return list


def align_feather_targets(arm_feathers_count, forearm_feathers_count, hand_feathers_count, side):
    arm_feather_stretch = get_edit_bone("arm_feather_stretch" + side)
    forearm_feather_stretch = get_edit_bone("forearm_feather_stretch" + side)
    hand_feather_stretch = get_edit_bone("hand_feather_stretch" + side)

    # arm
    for fi in range(1, arm_feathers_count + 1):
        featheridx = "{0:0=2d}".format(fi)
        arm_f_name = "arm_feather_" + featheridx + side
        arm_f = get_edit_bone(arm_f_name)

        target_name = arm_f_name.replace('feather_', 'feather_target_')
        arm_f_target = get_edit_bone(target_name)
        p1 = project_point_onto_line(arm_feather_stretch.head, arm_feather_stretch.tail, arm_f.tail)
        p2 = project_point_onto_line(arm_f.head, arm_f.tail, p1)
        arm_f_target.head = p2
        arm_f_target.tail = arm_f_target.head + (arm_f.z_axis.normalized() * arm_f.length * 0.1)

    # forearm
    for fi in range(1, forearm_feathers_count + 1):
        featheridx = "{0:0=2d}".format(fi)
        forearm_f_name = "forearm_feather_" + featheridx + side
        forearm_f = get_edit_bone(forearm_f_name)

        target_name = forearm_f_name.replace('feather_', 'feather_target_')
        forearm_f_target = get_edit_bone(target_name)
        p1 = project_point_onto_line(forearm_feather_stretch.head, forearm_feather_stretch.tail, forearm_f.tail)
        p2 = project_point_onto_line(forearm_f.head, forearm_f.tail, p1)
        forearm_f_target.head = p2
        forearm_f_target.tail = forearm_f_target.head + (forearm_f.z_axis.normalized() * forearm_f.length * 0.1)

    # hand
    for fi in range(1, hand_feathers_count):
        if fi == hand_feathers_count:  # the last bone don't need it
            continue

        featheridx = "{0:0=2d}".format(fi)
        hand_f_name = "hand_feather_" + featheridx + side
        hand_f = get_edit_bone(hand_f_name)

        target_name = hand_f_name.replace('feather_', 'feather_target_')
        hand_f_target = get_edit_bone(target_name)
        p1 = project_point_onto_line(hand_feather_stretch.head, hand_feather_stretch.tail, hand_f.tail)
        p2 = project_point_onto_line(hand_f.head, hand_f.tail, p1)
        hand_f_target.head = p2
        hand_f_target.tail = hand_f_target.head + (hand_f.z_axis.normalized() * hand_f.length * 0.1)


def align_feather_mid_targets(side):
    # arm
    arm_feather_mid_target_name = "arm_feather_mid_target" + side
    arm_feather_mid_target = get_edit_bone(arm_feather_mid_target_name)

    c_arm_mid_name = "c_arm_feather_mid" + side
    c_arm_feather_mid = get_edit_bone(c_arm_mid_name)

    arm_feather_mid_target.head = c_arm_feather_mid.head
    arm_feather_mid_target.tail = c_arm_feather_mid.head + (c_arm_feather_mid.tail - c_arm_feather_mid.head) * 0.5
    arm_feather_mid_target.roll = c_arm_feather_mid.roll

    # forearm
    forearm_feather_mid_target_name = "forearm_feather_mid_target" + side
    forearm_feather_mid_target = get_edit_bone(forearm_feather_mid_target_name)

    c_forearm_mid_name = "c_forearm_feather_mid" + side
    c_forearm_feather_mid = get_edit_bone(c_forearm_mid_name)

    forearm_feather_mid_target.head = c_forearm_feather_mid.head
    forearm_feather_mid_target.tail = c_forearm_feather_mid.head + (
            c_forearm_feather_mid.tail - c_forearm_feather_mid.head) * 0.5
    forearm_feather_mid_target.roll = c_forearm_feather_mid.roll

    # hand
    hand_feather_mid_target_name = "hand_feather_mid_target" + side
    hand_feather_mid_target = get_edit_bone(hand_feather_mid_target_name)

    c_hand_mid_name = "c_hand_feather_mid" + side
    c_hand_feather_mid = get_edit_bone(c_hand_mid_name)

    hand_feather_mid_target.head = c_hand_feather_mid.head
    hand_feather_mid_target.tail = c_hand_feather_mid.head + (c_hand_feather_mid.tail - c_hand_feather_mid.head) * 0.5
    hand_feather_mid_target.roll = c_hand_feather_mid.roll


def align_feather_main(arm_feathers_count, forearm_feathers_count, hand_feathers_count, feathers_layers, side):
    last_layer_idx = "{0:0=2d}".format(feathers_layers)

    # arm
    arm_stretch = get_edit_bone("arm_stretch" + side)
    for i in range(1, arm_feathers_count + 1):
        featheridx = "{0:0=2d}".format(i)
        feather_ref_name = "arm_feather_" + featheridx + "_" + last_layer_idx + "_ref" + side
        feather_ref = get_edit_bone(feather_ref_name)
        main_f_name = "arm_feather_" + featheridx + side
        main_f = get_edit_bone(main_f_name)

        # set transforms
        main_f.head = feather_ref.head
        main_f.tail = feather_ref.tail
        main_f.roll = feather_ref.roll

    # forearm
    forearm_stretch = get_edit_bone("forearm_stretch" + side)
    for i in range(1, forearm_feathers_count + 1):
        featheridx = "{0:0=2d}".format(i)
        feather_ref_name = "forearm_feather_" + featheridx + "_" + last_layer_idx + "_ref" + side
        feather_ref = get_edit_bone(feather_ref_name)
        main_f_name = "forearm_feather_" + featheridx + side
        main_f = get_edit_bone(main_f_name)

        # set transforms
        main_f.head = feather_ref.head
        main_f.tail = feather_ref.tail
        main_f.roll = feather_ref.roll

    # hand
    hand = get_edit_bone("hand" + side)
    for i in range(1, hand_feathers_count + 1):
        featheridx = "{0:0=2d}".format(i)
        feather_ref_name = "hand_feather_" + featheridx + "_" + last_layer_idx + "_ref" + side
        feather_ref = get_edit_bone(feather_ref_name)
        main_f_name = "hand_feather_" + featheridx + side
        main_f = get_edit_bone(main_f_name)

        # set transforms
        main_f.head = feather_ref.head
        main_f.tail = feather_ref.tail
        main_f.roll = feather_ref.roll

        if i == hand_feathers_count:
            hand_feather_master_name = "c_hand_feather_master" + side
            hand_feather_master = get_edit_bone(hand_feather_master_name)
            align_feather_hand_master(hand_feather_master_name, main_f)


def align_feather_hand_master(bname, last_hand_feather):
    hand_feather_master = get_edit_bone(bname)
    hand_feather_master.head, hand_feather_master.tail, hand_feather_master.roll = last_hand_feather.head.copy(), last_hand_feather.tail.copy(), last_hand_feather.roll
    hand_feather_master.tail += (hand_feather_master.tail - hand_feather_master.head) * 0.2


def align_feather_controls(arm_feathers_count, forearm_feathers_count, hand_feathers_count, feathers_layers,
                           feathers_subdiv, side):
    # arm
    for i in range(1, arm_feathers_count + 1):
        featheridx = "{0:0=2d}".format(i)

        for j in range(1, feathers_layers + 1):
            layeridx = "{0:0=2d}".format(j)
            feather_ref_name = "arm_feather_" + featheridx + "_" + layeridx + "_ref" + side
            feather_ref = get_edit_bone(feather_ref_name)

            layer_vector = feather_ref.tail - feather_ref.head

            for k in range(1, feathers_subdiv + 1):
                subdividx = "{0:0=2d}".format(k)
                c_bone_name = "c_arm_feather_" + featheridx + '_' + layeridx + '_' + subdividx + side
                c_bone = get_edit_bone(c_bone_name)

                # set transforms
                c_bone.head = feather_ref.head + ((layer_vector / feathers_subdiv) * (k - 1))
                c_bone.tail = c_bone.head + (layer_vector / feathers_subdiv)
                c_bone.roll = feather_ref.roll

    # forearm
    for i in range(1, forearm_feathers_count + 1):
        featheridx = "{0:0=2d}".format(i)

        for j in range(1, feathers_layers + 1):
            layeridx = "{0:0=2d}".format(j)
            feather_ref_name = "forearm_feather_" + featheridx + "_" + layeridx + "_ref" + side
            feather_ref = get_edit_bone(feather_ref_name)

            layer_vector = feather_ref.tail - feather_ref.head

            for k in range(1, feathers_subdiv + 1):
                subdividx = "{0:0=2d}".format(k)
                c_bone_name = "c_forearm_feather_" + featheridx + '_' + layeridx + '_' + subdividx + side
                c_bone = get_edit_bone(c_bone_name)

                # set transforms
                c_bone.head = feather_ref.head + ((layer_vector / feathers_subdiv) * (k - 1))
                c_bone.tail = c_bone.head + (layer_vector / feathers_subdiv)
                c_bone.roll = feather_ref.roll

    # hand
    for i in range(1, hand_feathers_count + 1):
        featheridx = "{0:0=2d}".format(i)

        for j in range(1, feathers_layers + 1):
            layeridx = "{0:0=2d}".format(j)
            feather_ref_name = "hand_feather_" + featheridx + "_" + layeridx + "_ref" + side
            feather_ref = get_edit_bone(feather_ref_name)

            layer_vector = feather_ref.tail - feather_ref.head

            for k in range(1, feathers_subdiv + 1):
                subdividx = "{0:0=2d}".format(k)
                c_bone_name = "c_hand_feather_" + featheridx + '_' + layeridx + '_' + subdividx + side
                c_bone = get_edit_bone(c_bone_name)

                # set transforms
                c_bone.head = feather_ref.head + ((layer_vector / feathers_subdiv) * (k - 1))
                c_bone.tail = c_bone.head + (layer_vector / feathers_subdiv)
                c_bone.roll = feather_ref.roll


def align_feather_mid(first_arm_feather, last_arm_feather, first_forearm_feather, last_forearm_feather,
                      first_hand_feather, side):
    # arm
    c_arm_feather_mid = get_edit_bone("c_arm_feather_mid" + side)
    arm_ref = get_edit_bone("arm_ref" + side)
    c_arm_feather_mid.head = arm_ref.head
    c_arm_feather_mid.tail = first_arm_feather.tail
    align_bone_x_axis(c_arm_feather_mid, first_arm_feather.x_axis)

    # forearm
    c_forearm_feather_mid = get_edit_bone("c_forearm_feather_mid" + side)
    forearm_ref = get_edit_bone("forearm_ref" + side)
    c_forearm_feather_mid.head = forearm_ref.head
    c_forearm_feather_mid.tail = (last_arm_feather.tail + first_forearm_feather.tail) / 2
    align_bone_x_axis(c_forearm_feather_mid, first_forearm_feather.x_axis)

    # hand
    c_hand_feather_mid = get_edit_bone("c_hand_feather_mid" + side)
    hand_ref = get_edit_bone("hand_ref" + side)
    c_hand_feather_mid.head = hand_ref.head
    c_hand_feather_mid.tail = (last_forearm_feather.tail + first_hand_feather.tail) / 2
    align_bone_x_axis(c_hand_feather_mid, first_hand_feather.x_axis)

    return c_arm_feather_mid, c_forearm_feather_mid, c_hand_feather_mid


def align_feather_stretches(last_hand_feather, side):
    c_arm_feather_mid = get_edit_bone("c_arm_feather_mid" + side)
    c_forearm_feather_mid = get_edit_bone("c_forearm_feather_mid" + side)
    c_hand_feather_mid = get_edit_bone("c_hand_feather_mid" + side)

    # arm
    arm_feather_stretch = get_edit_bone("arm_feather_stretch" + side)
    arm_feather_stretch.head = c_arm_feather_mid.tail
    arm_feather_stretch.tail = c_forearm_feather_mid.tail
    align_bone_x_axis(arm_feather_stretch, c_forearm_feather_mid.z_axis)

    # forearm
    forearm_feather_stretch = get_edit_bone("forearm_feather_stretch" + side)
    forearm_feather_stretch.head = arm_feather_stretch.tail
    forearm_feather_stretch.tail = c_hand_feather_mid.tail
    align_bone_x_axis(forearm_feather_stretch, c_hand_feather_mid.z_axis)

    # hand
    hand_feather_stretch = get_edit_bone("hand_feather_stretch" + side)
    hand_feather_stretch.head = forearm_feather_stretch.tail
    hand_feather_stretch.tail = last_hand_feather.tail
    align_bone_x_axis(hand_feather_stretch, last_hand_feather.z_axis)


def get_mirror_side(current_side):
    if "l" in current_side:
        return current_side.replace("l", "r")
    elif "L" in current_side:
        return current_side.replace("L", "R")
    elif "r" in current_side:
        return current_side.replace("r", "l")
    elif "R" in current_side:
        return current_side.replace("R", "L")


def _mirror_shape_keys():
    mesh_obj = bpy.context.active_object
    shape_keys = mesh_obj.data.shape_keys.key_blocks
    has_driver = True
    drivers_list = None

    try:
        drivers_list = mesh_obj.data.shape_keys.animation_data.drivers
    except:
        print("no drivers founds")
        has_driver = False

    sides_letters = [".l", ".r", ".R", ".L",  "_l",  "_r",  "_L", "_R", "-l", "-r", "-L", "-R"]


    for key_block in shape_keys:

        current_side = key_block.name[-2:]
        if not current_side in sides_letters:
            continue

        mirror_side = get_mirror_side(current_side)

        mirror_sk_name = key_block.name[:-2] + mirror_side
        mirror_sk = shape_keys.get(mirror_sk_name)

        if mirror_sk == None:
            print("Mirror shape:", mirror_sk_name)
            # create
            new_shape = mesh_obj.shape_key_add(name="new", from_mix=False)
            new_shape.name = mirror_sk_name

            # copy value
            new_shape.value = key_block.value

            # copy min and max
            new_shape.slider_min = key_block.slider_min
            new_shape.slider_max = key_block.slider_max

            # copy vertices data
            mesh_obj.active_shape_key_index = len(shape_keys) - 1
            bpy.ops.object.mode_set(mode='EDIT')
            bpy.ops.mesh.select_all(action='SELECT')
            bpy.ops.mesh.blend_from_shape(shape=key_block.name, blend=1.0, add=False)
            bpy.ops.object.mode_set(mode='OBJECT')

            # mirror
            bpy.ops.object.shape_key_mirror(use_topology=False)

            # copy driver
            if has_driver:
                #for dr in drivers_list:
                #    if dr.data_path.startswith('key_blocks["'):
                sk_name = key_block.name
                dp = 'key_blocks["'+sk_name+'"].value'
                dr = drivers_list.find(dp)
                if dr == None:
                    continue

                mirror_dp = 'key_blocks["'+mirror_sk_name+'"].value'
                if drivers_list.find(mirror_dp) != None:
                    continue

                mirror_driver = mesh_obj.data.shape_keys.animation_data.drivers.from_existing(src_driver=dr)
                mirror_driver.driver.expression = dr.driver.expression
                mirror_driver.data_path = mirror_dp

                # mirror bones targets
                for var in mirror_driver.driver.variables:
                    rig = None
                    for tar in var.targets:
                        # targets
                        if tar.bone_target:
                            base_tar_bone_name = tar.bone_target
                            b_current_side = base_tar_bone_name[-2:]

                            if not b_current_side in sides_letters:
                                continue

                            b_mirror_side = get_mirror_side(b_current_side)
                            tar.bone_target = tar.bone_target[:-2] + b_mirror_side

                            # automatically create mirrored bones of rotational difference drivers if missing
                            if var.type == "ROTATION_DIFF":
                                rig = tar.id
                                if rig.data.bones.get(tar.bone_target) == None:
                                    set_active_object(rig.name)

                                    bpy.ops.object.mode_set(mode='EDIT')

                                    mirror_x_state = rig.data.use_mirror_x
                                    rig.data.use_mirror_x = False

                                    base_tar_bone = get_edit_bone(base_tar_bone_name)
                                    mirror_tar_bone = rig.data.edit_bones.new(tar.bone_target)

                                    # set transforms
                                    copy_bone_transforms(base_tar_bone, mirror_tar_bone)
                                    mirror_tar_bone.head[0] *= -1
                                    mirror_tar_bone.tail[0] *= -1
                                    mirror_tar_bone.roll = -base_tar_bone.roll

                                    # no deform
                                    mirror_tar_bone.use_deform = False

                                    # set layers
                                    mirror_tar_bone.layers[31] = True
                                    for i, j in enumerate(base_tar_bone.layers):
                                        mirror_tar_bone.layers[i] = j

                                    # set parent
                                    mirror_tar_bone.parent = get_edit_bone(base_tar_bone.parent.name[:-2]+b_mirror_side)

                                    rig.data.use_mirror_x = mirror_x_state

                                    bpy.ops.object.mode_set(mode='OBJECT')
                                    set_active_object(mesh_obj.name)

                        # data path
                        if tar.data_path:
                            dp = tar.data_path
                            if dp.startswith('pose.bones["'):
                                bname = dp.split('"')[1]
                                b_current_side = bname[-2:]

                                if b_current_side in sides_letters:
                                    b_mirror_side = get_mirror_side(b_current_side)
                                    mirror_bname = bname[:-2] + b_mirror_side
                                    tar.data_path = tar.data_path.replace(bname, mirror_bname)


def _cancel_corrective_driver(self):
    scene = bpy.context.scene
    current_mode = bpy.context.mode

    if scene.arp_corrective_shapes_data == "":
        return

    data_list = scene.arp_corrective_shapes_data.split(',')

    if len(data_list) != 4:
        # reset the data property
        scene["arp_corrective_shapes_data"] = ""
        return

    rotated_bone_name = data_list[0]
    rig_name = data_list[3]

    set_active_object(rig_name)

    # delete the rotated bone
    bpy.ops.object.mode_set(mode='EDIT')

    # save X-Mirror state
    xmirror_state = bpy.context.object.data.use_mirror_x
    bpy.context.object.data.use_mirror_x = False

    rotated_bone = get_edit_bone(rotated_bone_name)
    if rotated_bone:
        delete_edit_bone(rotated_bone)

    # restore X-Mirror state
    bpy.context.object.data.use_mirror_x = xmirror_state

    # reset the data property
    scene["arp_corrective_shapes_data"] = ""

    # Restore saved mode
    restore_current_mode(current_mode)


def _add_corrective_driver(self):
    scene = bpy.context.scene
    data_list = scene.arp_corrective_shapes_data.split(',')
    rig_name = data_list[3]
    rotated_bone = data_list[0]
    source_bone = data_list[1]
    angle = data_list[2]

    rig = bpy.data.objects.get(rig_name)
    obj_mesh = bpy.context.active_object
    if obj_mesh.data.shape_keys == None:
        self.report({'ERROR'}, 'Select the shape key to add the driver to')
        return

    shape_keys = obj_mesh.data.shape_keys.key_blocks
    shape_index = bpy.context.active_object.active_shape_key_index

    # create driver
    if obj_mesh.data.shape_keys.animation_data == None:
        obj_mesh.data.shape_keys.animation_data_create()

    drivers_list = obj_mesh.data.shape_keys.animation_data.drivers
    # does the driver already exist?
    is_already_created = drivers_list.find('key_blocks["' + shape_keys[shape_index].name + '"].value')
    if is_already_created:
        print("The driver already exists")
        return

    #   create it
    new_driver = shape_keys[shape_index].driver_add("value")

    new_driver.driver.expression = '(' + angle + '-var) / ' + angle
    new_var = new_driver.driver.variables.new()
    new_var.type = 'ROTATION_DIFF'
    new_var.targets[0].id = rig
    new_var.targets[0].bone_target = rotated_bone
    new_var.targets[1].id = rig
    new_var.targets[1].bone_target = source_bone

    # reset the corrective shapes property data
    scene["arp_corrective_shapes_data"] = ""

    print("Driver created")


def _add_corrective_bone(self):
    rig = bpy.context.active_object
    if len(get_selected_pose_bones()) == 0:
        rig.data.layers[31] = True
        rig.show_in_front = True
        self.report({'ERROR'}, 'Select a bone')
        return
    
    sel_bone = get_selected_pose_bones()[0]
    type = ""

    # get the bone side
    b_name = sel_bone.name
    side = get_bone_side(b_name)
    
    # is the selection valid?
    # is it a rotated bone? in this case go to the next step
    if len(b_name.split('_')) >= 2:
        if b_name.split('_')[1] == 'rotated':
            bone_data = rig.data.bones[b_name]
            if len(bone_data.keys()):
                if 'arp_driver_data' in bone_data.keys():
                    bpy.context.scene.arp_corrective_shapes_data = bone_data['arp_driver_data']
                    self.report({'INFO'}, 'Existing rotated bone selected')
                    return

    # is it a valid deformign bone?
    valid_bones = ['leg_stretch', 'leg_twist', 'thigh_stretch', 'thigh_twist', 'forearm_twist', 'forearm_stretch',
                   'arm_stretch', 'c_arm_twist', 'arm_twist']
    valid = False
    for b in valid_bones:
        if b_name.startswith(b) or b_name.startswith('c_' + b):
            if sel_bone.bone.use_deform:
                valid = True
                break

    if not valid:
        rig.data.layers[31] = True
        rig.show_in_front = True
        self.report({'ERROR'}, 'Invalid bone selected. Select 1 leg or arm deforming bone, or 2 deforming bones\nto evaluate angle from')
        return

    # select the primary limb bone
    if rig.arp_secondary_type == "NONE" or rig.arp_secondary_type == "ADDITIVE":
        if sel_bone.name.startswith("thigh_stretch"):
            sel_bone = get_pose_bone("thigh_twist" + side)
        elif sel_bone.name.startswith('thigh_twist'):
            sel_bone = get_pose_bone('thigh_twist' + side)
        elif sel_bone.name.startswith("leg_twist"):
            sel_bone = get_pose_bone("leg_stretch" + side)
        elif sel_bone.name.startswith("arm_stretch"):
            sel_bone = get_pose_bone("c_arm_twist_offset" + side)
        elif sel_bone.name.startswith('arm_twist_'):
            sel_bone = get_pose_bone('c_arm_twist_offset' + side)
        elif sel_bone.name.startswith("forearm_twist"):
            sel_bone = get_pose_bone("forearm_stretch" + side)


    elif rig.arp_secondary_type == "TWIST_BASED":
        if sel_bone.name.startswith("c_arm_twist") or sel_bone.name.startswith("c_arm_stretch"):
            sel_bone = get_pose_bone("c_arm_twist" + side)
        elif sel_bone.name.startswith("c_forearm_twist"):
            sel_bone = get_pose_bone("c_forearm_stretch" + side)
        elif sel_bone.name.startswith('c_thigh_twist') or sel_bone.name.startswith('c_thigh_stretch'):
            sel_bone = get_pose_bone('c_thigh_twist' + side)
        elif sel_bone.name.startswith('c_leg_twist'):
            sel_bone = get_pose_bone('c_leg_stretch' + side)

    if sel_bone == None:        
        return

    b_name = sel_bone.name

    if "thigh" in sel_bone.name:
        type = "thigh"
    elif "leg" in sel_bone.name:
        type = "leg"
    elif "arm" in sel_bone.name and not "forearm" in sel_bone.name:
        type = "arm"
    elif "forearm" in sel_bone.name:
        type = "forearm"

    par_name = None
    if type == "thigh":
        par_name = "c_thigh_b" + side
    elif type == "leg":
        par_name = "thigh_stretch" + side
    elif type == "arm":
        par_name = get_data_bone("c_arm_fk" + side).parent.name  # "shoulder"+side
    elif type == "forearm":
        par_name = "arm_stretch" + side

    if par_name == None:
        rig.data.layers[31] = True
        rig.show_in_front = True
        self.report({'ERROR'}, 'Invalid bone selected. Select 1 leg or arm deforming bone, or 2 deforming bones\nto evaluate angle from')
        return

    # print(par_name)
    par_pbone = get_pose_bone(par_name)
    bone_head = par_pbone.matrix.inverted() @ sel_bone.head.copy()
    bone_tail = par_pbone.matrix.inverted() @ sel_bone.tail.copy()
    bone_mat = par_pbone.matrix.inverted() @ sel_bone.matrix

    # create the rotated bone
    bpy.ops.object.mode_set(mode='EDIT')

    # save X-Mirror state
    xmirror_state = bpy.context.object.data.use_mirror_x
    bpy.context.object.data.use_mirror_x = False

    par_ebone = get_edit_bone(par_name)

    def get_id_str(v_int):
        return '%02d' % (v_int)

    id = 1
    rotated_bone_name = type + "_rotated_" + get_id_str(id) + side

    found_rot_bone = True
    while found_rot_bone:
        rotated_bone_name = type + "_rotated_" + get_id_str(id) + side
        new_bone = get_edit_bone(rotated_bone_name)
        if new_bone:
            id += 1
        else:
            found_rot_bone = False

    new_bone = rig.data.edit_bones.new(rotated_bone_name)
    new_bone.head = bone_head
    new_bone.tail = bone_tail
    new_bone.matrix = par_ebone.matrix @ bone_mat
    set_bone_layer(new_bone, 31)
    new_bone.parent = get_edit_bone(par_name)
    new_bone.use_deform = False
    new_bone.inherit_scale = "NONE"  # inheriting scale of stretchy bone leads to incorrect driver rotation evaluation

    source_bone = get_edit_bone(b_name)
    #bones_angle = round(new_bone.y_axis.angle(source_bone.y_axis), 10)
    q1 = new_bone.matrix.to_quaternion()
    q2 = source_bone.matrix.to_quaternion()
    q12 = q1.conjugated() @ q2
    q12_vec = Vector((q12[1], q12[2], q12[3]))
    bones_angle = 2 * atan2(q12_vec.magnitude, q12[0])

    bpy.context.scene.arp_corrective_shapes_data = new_bone.name + "," + b_name + "," + str(bones_angle) + "," + rig.name

    init_selection(new_bone.name)

    bpy.context.object.data.use_mirror_x = xmirror_state

    bpy.ops.object.mode_set(mode='POSE')

    # also save it in a prop of the rotated bone for later access
    rig.data.bones[rotated_bone_name]["arp_driver_data"] = bpy.context.scene.arp_corrective_shapes_data

    if type == "arm":
        # the arm rotated bone must have a location constraint
        rot_pbone = get_pose_bone(rotated_bone_name)
        cns = rot_pbone.constraints.new("COPY_LOCATION")
        cns.target = rig
        cns.subtarget = "c_shoulder" + side
        cns.head_tail = 1.0

        # only set a locked track constraint in non-bendy bones mode
        if rig.arp_secondary_type != "BENDY_BONES":
            cns2 = rot_pbone.constraints.new("LOCKED_TRACK")
            cns2.target = rig
            cns2.subtarget = "shoulder_pole" + side
            cns2.track_axis = "TRACK_NEGATIVE_Z"
            cns2.lock_axis = "LOCK_Y"

    rig.data.layers[31] = False
    rig.show_in_front = False
    self.report({'INFO'}, 'Bone Added: ' + rotated_bone_name)


def _add_corrective_bone_universal(self):
    scn = bpy.context.scene

    rig = bpy.context.active_object

    if len(get_selected_pose_bones()) != 2:
        rig.data.layers[31] = True
        rig.show_in_front = True
        self.report({'ERROR'}, 'Select 2 deforming bones')
        return

    sel_bone2 = bpy.context.active_pose_bone
    sel_bone1 = [i for i in get_selected_pose_bones() if i != sel_bone2][0]

    # get the bone side
    b1_name = sel_bone1.name
    b2_name = sel_bone2.name
    side = get_bone_side(b2_name)

    # is the selection valid?
    valid = True
    if not sel_bone2.bone.use_deform or not sel_bone1.bone.use_deform:
        valid = False

    if not valid:
        self.report({'ERROR'}, 'Select deforming bones only')
        return

        # is it a rotated bone? in this case go to the next step
    if len(b2_name.split('_')) >= 2:
        if b2_name.split('_')[1] == 'rotated':
            bone_data = rig.data.bones[b2_name]
            if len(bone_data.keys()) > 0:
                if 'arp_driver_data' in bone_data.keys():
                    bpy.context.scene.arp_corrective_shapes_data = bone_data['arp_driver_data']
                    self.report({'INFO'}, 'Existing rotated bone selected')
                    return

    bone_head = sel_bone1.matrix.inverted() @ sel_bone2.head.copy()
    bone_tail = sel_bone1.matrix.inverted() @ sel_bone2.tail.copy()
    bone_mat = sel_bone1.matrix.inverted() @ sel_bone2.matrix

    # create the rotated bone
    bpy.ops.object.mode_set(mode='EDIT')

    # save X-Mirror state
    xmirror_state = bpy.context.object.data.use_mirror_x
    bpy.context.active_object.data.use_mirror_x = False

    par_ebone = get_edit_bone(b1_name)

    def get_id_str(v_int):
        return '%02d' % (v_int)

    id = 1
    rotated_bone_name = b2_name.replace(side, "_rotated_" + get_id_str(id)) + side

    found_rot_bone = True
    while found_rot_bone:
        rotated_bone_name = b2_name.replace(side, "_rotated_" + get_id_str(id)) + side
        new_bone = get_edit_bone(rotated_bone_name)
        if new_bone:
            id += 1
        else:
            found_rot_bone = False

    new_bone = rig.data.edit_bones.new(rotated_bone_name)

    new_bone.head = bone_head
    new_bone.tail = bone_tail
    new_bone.matrix = par_ebone.matrix @ bone_mat
    set_bone_layer(new_bone, 31)
    new_bone.parent = get_edit_bone(b1_name)
    new_bone.use_deform = False
    new_bone.inherit_scale = "NONE"  # inheriting scale of stretchy bone leads to incorrect driver rotation evaluation

    source_bone = get_edit_bone(b2_name)
    #bones_angle = round(new_bone.y_axis.angle(source_bone.y_axis), 10)
    print(new_bone.name, source_bone.name)
    q1 = new_bone.matrix.to_quaternion()
    q2 = source_bone.matrix.to_quaternion()
    q12 = q1.conjugated() @ q2
    q12_vec = Vector((q12[1], q12[2], q12[3]))
    bones_angle = 2 * atan2(q12_vec.magnitude, q12[0])
    bones_angle = round(bones_angle, 5)

    scn.arp_corrective_shapes_data = new_bone.name + "," + b2_name + "," + str(
        bones_angle) + "," + rig.name

    init_selection(new_bone.name)

    bpy.context.object.data.use_mirror_x = xmirror_state

    bpy.ops.object.mode_set(mode='POSE')

    # also save it in a prop of the rotated bone for later access
    rig.data.bones[rotated_bone_name]["arp_driver_data"] = scn.arp_corrective_shapes_data

    rig.data.layers[31] = False
    rig.show_in_front = False
    self.report({'INFO'}, 'Bone Added: ' + rotated_bone_name)


def _set_pose(self):
    type = self.pose_type
    scn = bpy.context.scene
    arp_rig = bpy.context.active_object
    bones_set = []

    vec_dict = None
    matrix_dict = None
    if type == "APOSE":
        vec_dict = mannequin_coords.coords
        if scn.arp_retro_axes:
            matrix_dict = mannequin_coords.matrix_coords_legacy
        else:
            matrix_dict = mannequin_coords.matrix_coords
    elif type == "APOSE_MANNY":
        vec_dict = mannequin_coords.coords_manny
        if scn.arp_retro_axes:
            matrix_dict = mannequin_coords.matrix_manny_coords_legacy
        else:
            matrix_dict = mannequin_coords.matrix_manny_coords
    elif type == "TPOSE":
        vec_dict = mannequin_coords_tpose.coords


    # get current feet position to maintain feet on ground level
    bpy.ops.object.mode_set(mode='EDIT')

    foot_z = 0.0
    feet_count = 0

    for side in ['.l', '.r']:
        foot = get_edit_bone('foot_ref'+side)
        if foot:
            feet_count += 1
            foot_z += foot.head[2]

    if feet_count != 0:
        foot_z = foot_z/feet_count

    bpy.ops.object.mode_set(mode='OBJECT')


    for ue_bone in vec_dict:
        # translate bone name
        arp_bone_name = ""
        if ue_bone == "pelvis":
            arp_bone_name = "c_root_master.x"        
        elif ue_bone == "spine_01":
            arp_bone_name = "c_spine_01.x"
        elif ue_bone == "spine_02":
            arp_bone_name = "c_spine_02.x"
        elif ue_bone == "spine_03":
            arp_bone_name = "c_spine_03.x"
        elif ue_bone == "spine_04":
            arp_bone_name = "c_spine_04.x"
        elif ue_bone == "spine_05":
            arp_bone_name = "c_spine_05.x"
        elif ue_bone == "spine_06":
            arp_bone_name = "c_spine_06.x"
        elif ue_bone == "neck_01":
            arp_bone_name = "c_neck.x"
        elif ue_bone == "head":
            arp_bone_name = "c_head.x"
        elif ue_bone.startswith("clavicle_"):
            arp_bone_name = "c_shoulder." + ue_bone.split('_')[1]
        elif ue_bone.startswith("upperarm_"):
            arp_bone_name = "c_arm_fk." + ue_bone.split('_')[1]
        elif ue_bone.startswith("lowerarm_"):
            arp_bone_name = "c_forearm_fk." + ue_bone.split('_')[1]
        elif ue_bone.startswith("hand_"):
            arp_bone_name = "c_hand_fk." + ue_bone.split('_')[1]
        elif ue_bone.startswith("thigh_"):
            arp_bone_name = "c_thigh_fk." + ue_bone.split('_')[1]
        elif ue_bone.startswith("calf_"):
            arp_bone_name = "c_leg_fk." + ue_bone.split('_')[1]
        elif ue_bone.startswith("foot_"):
            arp_bone_name = "c_foot_fk." + ue_bone.split('_')[1]
        elif ue_bone.startswith("thumb_01_"):
            arp_bone_name = "c_thumb1." + ue_bone.split('_')[2]
        elif ue_bone.startswith("thumb_02_"):
            arp_bone_name = "c_thumb2." + ue_bone.split('_')[2]
        elif ue_bone.startswith("thumb_03_"):
            arp_bone_name = "c_thumb3." + ue_bone.split('_')[2]
        elif ue_bone.startswith("index_01_"):
            arp_bone_name = "c_index1." + ue_bone.split('_')[2]
        elif ue_bone.startswith("index_02_"):
            arp_bone_name = "c_index2." + ue_bone.split('_')[2]
        elif ue_bone.startswith("index_03_"):
            arp_bone_name = "c_index3." + ue_bone.split('_')[2]
        elif ue_bone.startswith("middle_01_"):
            arp_bone_name = "c_middle1." + ue_bone.split('_')[2]
        elif ue_bone.startswith("middle_02_"):
            arp_bone_name = "c_middle2." + ue_bone.split('_')[2]
        elif ue_bone.startswith("middle_03_"):
            arp_bone_name = "c_middle3." + ue_bone.split('_')[2]
        elif ue_bone.startswith("ring_01_"):
            arp_bone_name = "c_ring1." + ue_bone.split('_')[2]
        elif ue_bone.startswith("ring_02_"):
            arp_bone_name = "c_ring2." + ue_bone.split('_')[2]
        elif ue_bone.startswith("ring_03_"):
            arp_bone_name = "c_ring3." + ue_bone.split('_')[2]
        elif ue_bone.startswith("pinky_01_"):
            arp_bone_name = "c_pinky1." + ue_bone.split('_')[2]
        elif ue_bone.startswith("pinky_02_"):
            arp_bone_name = "c_pinky2." + ue_bone.split('_')[2]
        elif ue_bone.startswith("pinky_03_"):
            arp_bone_name = "c_pinky3." + ue_bone.split('_')[2]

        arp_pbone = arp_rig.pose.bones.get(arp_bone_name)

        if arp_pbone == None:           
            continue

        # hands and feet have to be set to FK
        if 'foot' in arp_bone_name or 'hand' in arp_bone_name:
            ik_bone = get_pose_bone(arp_bone_name.replace("_fk", "_ik"))
            ik_bone["ik_fk_switch"] = 1.0

        # reset bones transforms
        arp_pbone.rotation_euler = [0, 0, 0]
        arp_pbone.location = [0, 0, 0]

        # set pose
        
        #if matrix_dict and ("thumb" in ue_bone or "index" in ue_bone or "middle" in ue_bone or "ring" in ue_bone or "pinky" in ue_bone):
        if matrix_dict and self.exact_rotations:# use exact rotation matrix
            """
            arp_pbone.matrix = matrix_dict[ue_bone]
            arp_pbone.location = [0,0,0]
            """
            bpy.ops.object.mode_set(mode='OBJECT')
            bpy.ops.object.empty_add(type='PLAIN_AXES', radius=1, location=(0, 0, 0), rotation=(0, 0, 0))
            bpy.context.active_object.name = arp_bone_name + "_empty_matrix"
            bpy.context.active_object.matrix_world = matrix_dict[ue_bone]
            set_active_object(arp_rig.name)
            bpy.ops.object.mode_set(mode='POSE')
            arp_pbone = arp_rig.pose.bones.get(arp_bone_name)
            new_cns = arp_pbone.constraints.new('COPY_ROTATION')
            new_cns.name = 'arp_copy_rot_matrix'
            new_cns.target = get_object(arp_bone_name + "_empty_matrix")

            bones_set.append(arp_bone_name)

        else:# use vector rotation, preserving the bone roll/ Y rotation
            vec = vec_dict[ue_bone]
            empty_loc = (arp_rig.matrix_world @ arp_pbone.head) + (vec * 10000)
            empty_loc_up = None

            if 'foot' in arp_bone_name:
                empty_loc_up = (arp_rig.matrix_world @ arp_pbone.head) + (Vector((0, 0, 1)) * 1000)

            bpy.ops.object.mode_set(mode='OBJECT')
            bpy.ops.object.empty_add(type='PLAIN_AXES', radius=1, location=(empty_loc), rotation=(0, 0, 0))
            bpy.context.active_object.name = arp_bone_name + "_empty_track"

            if empty_loc_up:
                # make sure the foot bone is horizontal
                bpy.ops.object.empty_add(type='PLAIN_AXES', radius=1, location=(empty_loc_up), rotation=(0, 0, 0))
                bpy.context.object.name = arp_bone_name + "_empty_uptrack"

            set_active_object(arp_rig.name)
            bpy.ops.object.mode_set(mode='POSE')
            arp_pbone = arp_rig.pose.bones.get(arp_bone_name)
            new_cns = arp_pbone.constraints.new('DAMPED_TRACK')
            new_cns.name = 'arp_damped_track'
            new_cns.target = bpy.data.objects[arp_bone_name + "_empty_track"]

            if empty_loc_up:
                new_cns2 = arp_pbone.constraints.new('DAMPED_TRACK')
                new_cns2.name = 'arp_damped_track_up'
                if scn.arp_retro_axes:#backward-compatibility
                    new_cns2.track_axis = "TRACK_NEGATIVE_Z"
                else:
                    new_cns2.track_axis = "TRACK_Z"
                
                new_cns2.target = bpy.data.objects[arp_bone_name + "_empty_uptrack"]

            bones_set.append(arp_bone_name)

    # refresh
    bpy.context.scene.frame_set(bpy.context.scene.frame_current)

    for b in bones_set:
        arp_pbone = arp_rig.pose.bones.get(b)
        # store the bone transforms
        bone_mat = arp_pbone.matrix.copy()

        # clear constraints
        for cns_name in ['arp_damped_track', 'arp_damped_track_up', 'arp_copy_rot_matrix']:
            cns = arp_pbone.constraints.get(cns_name)
            if cns:
                arp_pbone.constraints.remove(cns)

        # restore the transforms
        arp_pbone.matrix = bone_mat

    # clear empties helpers
    for object in bpy.data.objects:
        if '_empty_track' in object.name or '_empty_uptrack' in object.name or "_empty_matrix" in object.name:
            bpy.data.objects.remove(object, do_unlink=True)

    # maintain feet offset from the ground
    new_foot_z = 0.0

    for side in ['.l', '.r']:
        foot = get_pose_bone('foot'+side)
        if foot:
            loc, rot, scale = foot.matrix.decompose()
            new_foot_z += loc[2]
           
    if feet_count != 0:
        new_foot_z = new_foot_z/feet_count


    c_root_master = get_pose_bone(ard.spine_bones_dict['c_root_master'])
    if c_root_master:
        c_root_master.location[1] += -(new_foot_z - foot_z)


def _apply_pose_preserve_anim(self):
    # match to rig first, to apply new transforms to controllers
    bpy.ops.arp.match_to_rig()
    
    print("Preserving anims, init...")
    
    # get actions to preserve
    act_list = []
    for act in bpy.data.actions:
        if len(act.keys()):
            if "arp_apply_pose" in act.keys():
                if act["arp_apply_pose"] == True:
                    act_list.append(act)                
    
    arm_temp_name = self.rig.name+"_POSECOPY"
    arm_temp = get_object(arm_temp_name)
    
    # store current action
    base_action_name = ''
    if self.rig.animation_data.action:
        base_action_name = self.rig.animation_data.action.name
    
    for act in act_list:
        print("  "+act.name+'...')
        # get animated controllers
        anim_bones = []                    
        for fc in act.fcurves:
            if fc.data_path.startswith('pose.bones['):
                bname = fc.data_path.split('"')[1]
                if get_data_bone(bname):# ghost bones may have saved anim data, while not existing actually
                    if not bname in anim_bones:
                        anim_bones.append(bname)
                
        bpy.ops.object.mode_set(mode='OBJECT')
        bpy.ops.object.select_all(action='DESELECT')
        set_active_object(self.rig.name)
        bpy.ops.object.mode_set(mode='POSE')
        bpy.ops.pose.select_all(action='DESELECT')
        
        # add constraints                   
        for bname in anim_bones:
            pb = get_pose_bone(bname)  
            cns = pb.constraints.new('COPY_TRANSFORMS')
            cns.name = 'CopyTransforms_POSECOPY'
            cns.target = arm_temp
            cns.subtarget = bname
            if bname.startswith('c_arm_ik') or bname.startswith('c_thigh_ik'):# the IK arrow controller requires local space
                cns.owner_space = cns.target_space = 'LOCAL'
            # select for baking
            pb.bone.select = True
            
        
        # set temp armature action
        arm_temp.animation_data.action = act
        
        # get current keyframes position
        current_keyf_dict = None
        
        if self.key_all_frames == False:
            current_keyf_dict = {}
            for bname in anim_bones:
                pb = get_pose_bone(bname)
                current_keyf_dict[bname] = get_bone_keyframes_list(pb, act)
               
        
        # bake                       
        bake_anim(frame_start=act.frame_range[0], frame_end=act.frame_range[1], only_selected=True, 
                bake_bones=True, bake_object=False, new_action_name=act.name+'_PRESERVED', keyframes_dict=current_keyf_dict,
                interpolation_type='BEZIER')
        
        # remove constraints
        for bname in anim_bones:
            pb = get_pose_bone(bname)                        
            cns = pb.constraints.get('CopyTransforms_POSECOPY') 
            pb.constraints.remove(cns)
    
    # Done, remove temp armature
    delete_object(arm_temp)
        
    # set base action
    self.rig.animation_data.action = bpy.data.actions.get(base_action_name+'_PRESERVED')
    
    print("  Animations preserved.")
        
        
def _apply_pose_as_rest(self):
    limb_sides.get_multi_limbs()
    scn = bpy.context.scene
    mirror_state = bpy.context.object.data.use_mirror_x
    bpy.context.object.data.use_mirror_x = False    
    
    # 1.Apply armature modifiers of meshes
    rig = self.rig
    _rig_add = self.rig_add
    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.select_all(action='DESELECT')

    shape_keys_objects = []
    skinned_objects_dict = {}
    """
    for obj in bpy.data.objects:
        if len(obj.modifiers) == 0 or obj.type != "MESH" or is_object_hidden(obj):
            continue
            
        for modindex, mod in enumerate(obj.modifiers):
            if mod.type != "ARMATURE":
                continue
            if (mod.object != rig and mod.object != _rig_add) or mod.object == None:
                continue
    """
    for obj in self.skinned_meshes:  
        if is_object_hidden(obj):# object visibility may be driven, then the  object hasn't been revealed. Skip it
            continue
            
        for modindex, mod in enumerate(obj.modifiers):
            if mod.type != "ARMATURE":
                continue
            if (mod.object != rig and mod.object != _rig_add) or mod.object == None:
                continue            
            
            # save the armature modifiers to restore them later
            if obj.name not in skinned_objects_dict:
                skinned_objects_dict[obj.name] = {}
            if mod.object:
                skinned_objects_dict[obj.name][mod.name] = [mod.object.name, mod.use_deform_preserve_volume,
                                                            mod.use_multi_modifier, modindex]

            # objects with shape keys are handled separately, since modifiers can't be applied here
            if obj.data.shape_keys:
                if not obj in shape_keys_objects:
                    shape_keys_objects.append(obj)
                continue

            # apply modifier
            set_active_object(obj.name)
            
            if mod.show_viewport:
                print("Applying modifier of", obj.name, mod.name)
                apply_modifier(mod.name)


    # handle objects with shape keys
    for obj_sk in shape_keys_objects:
        try:
            bpy.ops.object.mode_set(mode='OBJECT')
            bpy.ops.object.select_all(action='DESELECT')
            set_active_object(obj_sk.name)
        except:
            set_active_object(obj_sk.name)

        # duplicate the mesh
        print("duplicate...")        
        current_objs_name = [obj.name for obj in bpy.data.objects]
        duplicate_object()
        dupli_mesh = None

        for obj in bpy.data.objects:
            if obj.name not in current_objs_name:
                dupli_mesh = obj
                break

        # store driver variables with shape keys as target, since the mesh duplication does not preserve them
        sk_driver_dict = {}
        sk_anim_data = obj_sk.data.shape_keys.animation_data
        if sk_anim_data and sk_anim_data.drivers:
            for dr in sk_anim_data.drivers:
                for i, var in enumerate(dr.driver.variables):
                    if var.targets[0].id_type == 'KEY':
                        target_id = var.targets[0].id
                        if target_id:
                            sk_driver_dict[dr.data_path+' '+var.name] = dr.data_path, var.name, target_id.name          
                        
                        
        # delete shape keys on the original mesh
        print("remove shape keys data...")
        set_active_object(obj_sk.name)
        for i in reversed(range(len(obj_sk.data.shape_keys.key_blocks))):
            #print("remove sk", obj_sk.data.shape_keys.key_blocks[i])
            obj_sk.active_shape_key_index = i
            bpy.ops.object.shape_key_remove()

        # apply modifiers
        for mod in obj_sk.modifiers:
            if mod.type != "ARMATURE":
                continue
            if mod.use_multi_modifier:  # do not apply if "multi modifier" is enabled, incorrect result... skip for now
                obj_sk.modifiers.remove(mod)
                continue
            if (mod.object != rig and mod.object != _rig_add) or mod.object == None:
                continue

            print("Applying modifier of", obj_sk.name, mod.name)
            set_active_object(obj_sk.name)
            apply_modifier(mod.name)

        # transfer shape keys
        print("transfer shape keys data...")
        transfer_shape_keys_deformed(dupli_mesh, obj_sk)
        
        # restore driver variables with shape keys as target
        for dp_var in sk_driver_dict:
            sk_anim_data = obj_sk.data.shape_keys.animation_data
            dp, var_name, shape_keys_name = sk_driver_dict[dp_var]            
            dr = sk_anim_data.drivers.find(dp)
            var = dr.driver.variables.get(var_name)
            var.targets[0].id = bpy.data.shape_keys.get(shape_keys_name)

        # delete duplicate
        if dupli_mesh:
            bpy.data.objects.remove(dupli_mesh, do_unlink=True)

    # Restore modifiers
    try:
        bpy.ops.object.mode_set(mode='OBJECT')
        bpy.ops.object.select_all(action='DESELECT')
    except:
        pass
    
    print("Restoring modifiers...")
    
    for obj_name in skinned_objects_dict:
        print('  '+obj_name)
        set_active_object(obj_name)
        _obj = get_object(obj_name)
        
        for mod_name in skinned_objects_dict[obj_name]:
            print("    set mod", mod_name)
            new_mod = _obj.modifiers.new(type="ARMATURE", name=mod_name)
            arm_name = skinned_objects_dict[obj_name][mod_name][0]
            preserve_bool = skinned_objects_dict[obj_name][mod_name][1]
            use_multi = skinned_objects_dict[obj_name][mod_name][2]
            new_mod.object = bpy.data.objects[arm_name]
            new_mod.use_deform_preserve_volume = preserve_bool
            new_mod.use_multi_modifier = use_multi

        def get_current_mod_index(mod_name):
            mod_dict = {}  
            if bpy.context.active_object:
                for i, mod in enumerate(bpy.context.active_object.modifiers):
                    mod_dict[mod.name] = i
            return mod_dict[mod_name]

        # re-order the modifiers stack
        for mod_name in skinned_objects_dict[obj_name]:
            target_index = skinned_objects_dict[obj_name][mod_name][3]
            current_index = get_current_mod_index(mod_name)
            move_delta = current_index - target_index
            if move_delta == 0:
                continue
            for i in range(0, abs(move_delta)):
                if move_delta < 0:
                    bpy.ops.object.modifier_move_down(modifier=mod_name)
                else:
                    bpy.ops.object.modifier_move_up(modifier=mod_name)

    # select the rig only
    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.select_all(action='DESELECT')
    
    set_active_object(rig.name)
    
    bpy.ops.object.mode_set(mode='POSE')

    # 2. Align the reference bones on the deforming bones
    # Store the current pose
    pose_bones_data = {}
    #   spine
    spine_map = {'root_ref.x': 'root.x'}
    for i in range(1, rig.rig_spine_count):
        idx = '%02d' % i
        spine_map['spine_'+idx+'_ref.x'] = 'spine_'+idx+'.x'
        
    for b_ref_name in spine_map:
        pbone_ref = get_pose_bone(b_ref_name)
        pbone_def = get_pose_bone(spine_map[b_ref_name])
        if pbone_def == None or pbone_ref == None:
            continue
        if b_ref_name == "root_ref.x":  # the root bone is inverted upside down
            pose_bones_data[b_ref_name] = [pbone_def.name, pbone_def.tail.copy(), pbone_def.head.copy(),
                                           mat3_to_vec_roll(pbone_def.matrix.to_3x3())]
        else:
            pose_bones_data[b_ref_name] = [pbone_def.name, pbone_def.head.copy(), pbone_def.tail.copy(),
                                           mat3_to_vec_roll(pbone_def.matrix.to_3x3())]

    #   breast
    breast_map = {'breast_01_ref': 'c_breast_01', 'breast_02_ref': 'c_breast_02'}
    for b_ref_name in breast_map:
        for subside in [".l", ".r"]:
            pbone_ref = get_pose_bone(b_ref_name + subside)
            pbone_def = get_pose_bone(breast_map[b_ref_name] + subside)
            if pbone_def == None or pbone_ref == None:
                continue
            pose_bones_data[pbone_ref.name] = [pbone_def.name, pbone_def.head.copy(), pbone_def.tail.copy(),
                                               mat3_to_vec_roll(pbone_def.matrix.to_3x3())]
                                               
    
    #   facial
    facial_map = {ard.neck_ref[0]: ard.neck_deform[1], ard.head_ref[0]: ard.heads_dict['deform'], 'eyebrow_full_ref': 'c_eyebrow_full',
                  'eyebrow_03_ref': 'c_eyebrow_03', 'eyebrow_02_ref': 'c_eyebrow_02', 'eyebrow_01_ref': 'c_eyebrow_01',
                  'eyebrow_01_end_ref': 'c_eyebrow_01_end', 'lips_top_ref.x': 'c_lips_top.x',
                  'lips_top_ref': 'c_lips_top', 'lips_smile_ref': 'c_lips_smile',
                  'lips_corner_mini_ref': 'c_lips_corner_mini', 'lips_bot_ref.x': 'c_lips_bot.x',
                  'lips_bot_ref': 'c_lips_bot',
                  'lips_roll_top_ref.x': 'c_lips_roll_top.x', 'lips_roll_bot_ref.x': 'c_lips_roll_bot.x',
                  'tong_01_ref.x': 'c_tong_01.x', 'tong_02_ref.x': 'c_tong_02.x', 'tong_03_ref.x': 'c_tong_03.x',
                  'teeth_bot_ref.x': 'c_teeth_bot.x', 'teeth_bot_ref': 'c_teeth_bot', 'chin_02_ref.x': 'c_chin_02.x',
                  'chin_01_ref.x': 'c_chin_01.x', 'teeth_top_ref.x': 'c_teeth_top.x', 'teeth_top_ref': 'c_teeth_top',
                  'eye_offset_ref': 'c_eye_offset', 'eyelid_top_ref': 'eyelid_top', 'eyelid_bot_ref': 'eyelid_bot',
                  'eyelid_top_01_ref': 'c_eyelid_top_01', 'eyelid_top_02_ref': 'c_eyelid_top_02',
                  'eyelid_top_03_ref': 'c_eyelid_top_03', 'eyelid_bot_01_ref': 'c_eyelid_bot_01',
                  'eyelid_bot_02_ref': 'c_eyelid_bot_02', 'eyelid_bot_03_ref': 'c_eyelid_bot_03',
                  'eyelid_corner_01_ref': 'c_eyelid_corner_01', 'eyelid_corner_02_ref': 'c_eyelid_corner_02',
                  'cheek_smile_ref': 'c_cheek_smile', 'cheek_inflate_ref': 'c_cheek_inflate',
                  'nose_01_ref.x': 'c_nose_01.x', 'nose_02_ref.x': 'c_nose_02.x', 'nose_03_ref.x': 'c_nose_03.x',
                  'jaw_ref.x': 'jawbone.x'}
    
    # multi lips
    for lip_ref_name in ard.get_variable_lips('.x', type='REFERENCE'):
        if not lip_ref_name.endswith('.x'):
            lip_ref_name = lip_ref_name[:-2]
        facial_map[lip_ref_name] = 'c_'+lip_ref_name.replace('_ref', '')
        
    # multi eyelids
    for eyel_ref_name in ard.get_variable_eyelids('.x', type='REFERENCE', no_side=True):     
        facial_map[eyel_ref_name] = 'c_'+eyel_ref_name.replace('_ref', '')
        
    for lvl in ['top', 'bot']:
        facial_map['eyelid_twk_'+lvl+'_ref'] = 'c_eyelid_twk_'+lvl
        
    for head_side in limb_sides.head_sides:
        suff = head_side[:-2]  # "" or "_dupli_001"...
        for subside in [".l", ".r"]:
            for b_ref_name in facial_map:
                final_ref_name = b_ref_name + suff + subside  # e.g. eyebrow_full_ref+''+'.l'
                final_def_name = facial_map[b_ref_name] + suff + subside  # e.g. c_eyebrow_full+''+'.l'

                if b_ref_name[-2:] == ".x":  # e.g. tong_03_ref.x
                    final_ref_name = b_ref_name[
                                     :-2] + suff + ".x"  # e.g. tong_03_ref+''+'.x' / tong_03_ref+'_dupli_001'+'.x'
                    final_def_name = facial_map[b_ref_name][:-2] + suff + '.x'

                pbone_ref = get_pose_bone(final_ref_name)
                pbone_def = get_pose_bone(final_def_name)
                if pbone_def == None or pbone_ref == None:
                    continue
                pose_bones_data[pbone_ref.name] = [pbone_def.name, pbone_def.head.copy(), pbone_def.tail.copy(),
                                                   mat3_to_vec_roll(pbone_def.matrix.to_3x3())]
                                                   
                                                   
        # subnecks
        for idx in range(1, 17):
            for pb in rig.pose.bones:
                if pb.name.startswith('c_subneck_' + str(idx) + head_side):
                    ref_name = 'subneck_' + str(idx) + '_ref' + head_side
                    pose_bones_data[ref_name] = [pb.name, pb.head.copy(), pb.tail.copy(),
                                                   mat3_to_vec_roll(pb.matrix.to_3x3())]
                                                   
                                                   
    # ears
    for ear_side in limb_sides.ear_sides:
        ears_list = ard.get_ears_controllers(ear_side)
        for c_ear_name in ears_list:            
            ref_name = c_ear_name[2:].replace(ear_side, '_ref'+ear_side)
            ref_pb = get_pose_bone(ref_name)
            c_pb = get_pose_bone(c_ear_name)
            print(ref_name, c_ear_name)
            pose_bones_data[ref_pb.name] = [c_pb.name, c_pb.head.copy(), c_pb.tail.copy(), mat3_to_vec_roll(c_pb.matrix.to_3x3())]
    

    #   arms
    arm_map = {'shoulder_ref': 'shoulder', 'arm_ref': 'arm', 'forearm_ref': 'forearm', 'hand_ref': 'hand',
               'pinky1_base_ref': 'c_pinky1_base', 'pinky1_ref': 'pinky1', 'pinky2_ref': 'c_pinky2',
               'pinky3_ref': 'c_pinky3', 'ring1_base_ref': 'c_ring1_base', 'ring1_ref': 'ring1', 'ring2_ref': 'c_ring2',
               'ring3_ref': 'c_ring3', 'middle1_base_ref': 'c_middle1_base', 'middle1_ref': 'middle1',
               'middle2_ref': 'c_middle2', 'middle3_ref': 'c_middle3', 'index1_base_ref': 'c_index1_base',
               'index1_ref': 'index1', 'index2_ref': 'c_index2', 'index3_ref': 'c_index3', 'thumb1_ref': 'thumb1',
               'thumb2_ref': 'c_thumb2', 'thumb3_ref': 'c_thumb3'}

    for arm_side in limb_sides.arm_sides:
        for b_ref_name in arm_map:
            pbone_ref = get_pose_bone(b_ref_name + arm_side)
            pbone_def = get_pose_bone(arm_map[b_ref_name] + arm_side)

            if pbone_def == None or pbone_ref == None:
                continue
            pose_bones_data[pbone_ref.name] = [pbone_def.name, pbone_def.head.copy(), pbone_def.tail.copy(),
                                               mat3_to_vec_roll(pbone_def.matrix.to_3x3())]

    #   legs
    leg_map = {'thigh_b_ref': 'c_thigh_b', 'thigh_ref': 'thigh', 'leg_ref': 'leg', 'foot_ref': 'foot', 'toes_ref': 'toes_01',
               'toes_pinky1_ref': 'c_toes_pinky1', 'toes_pinky2_ref': 'c_toes_pinky2',
               'toes_pinky3_ref': 'c_toes_pinky3', 'toes_ring1_ref': 'c_toes_ring1', 'toes_ring2_ref': 'c_toes_ring2',
               'toes_ring3_ref': 'c_toes_ring3', 'toes_middle1_ref': 'c_toes_middle1',
               'toes_middle2_ref': 'c_toes_middle2', 'toes_middle3_ref': 'c_toes_middle3',
               'toes_index1_ref': 'c_toes_index1', 'toes_index2_ref': 'c_toes_index2',
               'toes_index3_ref': 'c_toes_index3', 'toes_thumb1_ref': 'c_toes_thumb1',
               'toes_thumb2_ref': 'c_toes_thumb2', 'toes_thumb3_ref': 'c_toes_thumb3'}

    for leg_side in limb_sides.leg_sides:
        for b_ref_name in leg_map:
            pbone_ref = get_pose_bone(b_ref_name + leg_side)
            pbone_def = get_pose_bone(leg_map[b_ref_name] + leg_side)

            if pbone_def == None or pbone_ref == None:
                continue

            tail_pos = pbone_def.tail.copy()

            roll_val = mat3_to_vec_roll(pbone_def.matrix.to_3x3())
            # exception, get the roll from c_foot_01 for the toes bone
            if b_ref_name == 'toes_ref':
                c_foot_01 = get_pose_bone("toes_01" + leg_side)
                roll_val = mat3_to_vec_roll(c_foot_01.matrix.to_3x3())
                roll_val += radians(180)
                # and the length from c_toes_track
                c_toes_track = get_pose_bone("c_toes_track" + leg_side)
                tail_pos = pbone_def.head.copy() + ((c_toes_track.tail - c_toes_track.head).magnitude * (tail_pos - pbone_def.head.copy()).normalized())

            pose_bones_data[pbone_ref.name] = [pbone_def.name, pbone_def.head.copy(), tail_pos, roll_val]

            bpy.ops.object.mode_set(mode='EDIT')

            # store the foot initial foot matrix to shift bank bones as well later
            if b_ref_name == "foot_ref":
                bank_bones = ["foot_bank_01_ref", "foot_heel_ref", "foot_bank_02_ref"]
                for bank_name in bank_bones:
                    bank_bone = get_edit_bone(bank_name + leg_side)
                    bank_bone["arp_offset_matrix"] = get_edit_bone("foot_ref" + leg_side).matrix.inverted()

            bpy.ops.object.mode_set(mode='POSE')
            
    #   tails  
    for tside in limb_sides.tail_sides:      
        first_tail_name = "c_tail_00" + tside
        if get_data_bone(first_tail_name): 
        
            bpy.ops.object.mode_set(mode='EDIT')
            
            tail_count = get_tail_count(tside)
            
            bpy.ops.object.mode_set(mode='POSE')

            for i in range(0, tail_count):
                t_idx = '%02d' % i
                c_tail_name = 'c_tail_' + t_idx + tside

                c_pb = get_pose_bone(c_tail_name)
                if c_pb == None:
                    continue

                ref_name = 'tail_'+t_idx+'_ref'+tside                   
                roll_val = mat3_to_vec_roll(c_pb.matrix.to_3x3())
                pose_bones_data[ref_name] = [c_pb.name, c_pb.head.copy(), c_pb.tail.copy(), roll_val]
            
    
    # masters
    for n in ['c_pos', 'c_traj']:
        b = get_pose_bone(n)
        if b:
            roll_val = mat3_to_vec_roll(b.matrix.to_3x3())
            pose_bones_data[n] = [n, b.head.copy(), b.tail.copy(), roll_val]
    
    #   custom bones
    custom_pose_bones_data = {}   
    arp_bones_data.collect(rig.name)
    
    for bname in arp_bones_data.custom_bones_list:        
        pb = get_pose_bone(bname)
        roll_val = mat3_to_vec_roll(pb.matrix.to_3x3())
        custom_pose_bones_data[bname] = [pb.head.copy(), pb.tail.copy(), roll_val]
        

    # Apply to reference bones
    bpy.ops.object.mode_set(mode='EDIT')
    
    for b_ref_name in pose_bones_data:
        b_ref = get_edit_bone(b_ref_name)
        b_def = get_edit_bone(pose_bones_data[b_ref_name][0])
        b_ref.head, b_ref.tail, b_ref.roll = pose_bones_data[b_ref_name][1], pose_bones_data[b_ref_name][2], \
                                             pose_bones_data[b_ref_name][3]

        # shift the foot bank bones
        if "foot_ref" in b_ref_name:
            side = get_bone_side(b_ref_name)
            #side = b_ref_name[-2:]
            bank_bones = ["foot_bank_01_ref", "foot_heel_ref", "foot_bank_02_ref"]
            for bank_name in bank_bones:
                bank_bone = get_edit_bone(bank_name + side)
                if "arp_offset_matrix" in bank_bone.keys():
                    ob_mat = bpy.context.active_object.matrix_world.copy()
                    foot_mat_local = Matrix(bank_bone["arp_offset_matrix"]) @ ob_mat @ bank_bone.matrix
                    # move bank bone
                    bank_bone.matrix = b_ref.matrix @ foot_mat_local
                    # ensure Z axis is correct
                    fac = 1 if not scn.arp_retro_axes else -1# backward-compatibility
                    align_bone_z_axis(bank_bone, b_ref.z_axis * fac)
                    
    #   custom bones
    for bname in custom_pose_bones_data:
        eb = get_edit_bone(bname)
        eb.head, eb.tail, eb.roll = custom_pose_bones_data[bname][0], custom_pose_bones_data[bname][1], custom_pose_bones_data[bname][2]

    # Reset controllers
    bpy.ops.object.mode_set(mode='POSE')
    auto_rig_reset.reset_all()

    # display the reference bones layer
    _edit_ref()

    # restore XMirror state
    bpy.context.object.data.use_mirror_x = mirror_state


def refresh_rig_add(_rig):
    # delete current if any
    _rig_add = get_rig_add(_rig)
    if _rig_add:
        bpy.data.objects.remove(_rig_add, do_unlink=True)

    # add a new one
    arm_data = bpy.data.armatures.new("rig_add")

    new_rig_add = bpy.data.objects.new(_rig.name + "_add", arm_data)
    new_rig_add = bpy.data.objects[_rig.name + "_add"]
    new_rig_add.parent = _rig.parent

    # link to group
    for collec in _rig.users_collection:
        try:
            collec.objects.link(new_rig_add)
        except:
            pass

    cns_scale = new_rig_add.constraints.new("COPY_SCALE")
    cns_scale.target = _rig

    # assign the lost rig_add armature modifiers
    for obj in bpy.data.objects:
        if len(obj.modifiers) > 0:
            for mod in obj.modifiers:
                if mod.type == 'ARMATURE':
                    if mod.object == None and mod.name == "rig_add":
                        mod.object = new_rig_add

    return new_rig_add


def set_custom_shape(cs_name, target_bone):
    # only if the cs is not already edited
    if not "cs_user_" in get_pose_bone(target_bone).custom_shape.name:
        # append the cs into the scene if not found
        if bpy.data.objects.get(cs_name) == None:
            append_from_arp(nodes=[cs_name], type="object")

        # apply it
        get_pose_bone(target_bone).custom_shape = bpy.data.objects[cs_name]


def _add_limb(self, type):
    print("\nAdd limb:", type)    
   
    context = bpy.context
    scene = context.scene
    rig_name = bpy.context.active_object.name
    rig = get_object(rig_name)
    current_mode = bpy.context.mode
    bpy.ops.object.mode_set(mode='EDIT')

    # Save X-Mirror state
    xmirror_state = rig.data.use_mirror_x
    rig.data.use_mirror_x = False

    # custom limbs
    if type.endswith("_customlimb"):
        # import from file
        limbs_path = bpy.context.preferences.addons[__package__].preferences.custom_limb_path
        if not (limbs_path.endswith("\\") or limbs_path.endswith('/')):
            limbs_path += '/'
        filepath = limbs_path + type.replace("_customlimb", "") + ".py"
        file = open(filepath, 'rU')
        file_lines = file.readlines()
        bones_data_edit_raw = str(file_lines[0])
        bones_data_pose_raw = str(file_lines[1])
        bones_data_cns_raw = str(file_lines[2])
        bones_data_shape_raw = str(file_lines[3])
        bones_data_groups_raw = str(file_lines[4])
        bones_data_drivers_raw = str(file_lines[5])
        file.close()

        # import dicts
        bones_data_edit = ast.literal_eval(bones_data_edit_raw)
        bones_data_pose = ast.literal_eval(bones_data_pose_raw)
        bones_data_cns = ast.literal_eval(bones_data_cns_raw)
        bones_data_shape = ast.literal_eval(bones_data_shape_raw)
        bones_data_groups = ast.literal_eval(bones_data_groups_raw)
        bones_data_drivers = ast.literal_eval(bones_data_drivers_raw)
        # create bones
        create_bones_from_data(armature=rig, edit_data=bones_data_edit, pose_data=bones_data_pose,
                               cns_data=bones_data_cns, shape_data=bones_data_shape, groups_data=bones_data_groups,
                               drivers_data=bones_data_drivers)

    # built-in limbs
    else:
        # Get the dupli_id for naming
        bpy.ops.armature.select_all(action='DESELECT')
        side = type[-2:]
        if not "." in side:
            side = ".x"

        dupli_id, found_base = get_next_dupli_id(side, type)  # 001, True
        print("dupli_id found", dupli_id)
        
        bpy.ops.object.mode_set(mode='OBJECT')

        # pre-check for single limbs
        single_limbs = ['spine', 'breast']
        single_limb_exists = False

        if type in single_limbs:
            if found_base:
                single_limb_exists = True
                self.report_message = '"' + type.title() + '" is a single limb. Cannot be added multiple times for now.'
                print(self.report_message)

        # -- Add limbs --
        # Generated limbs
        if type == "ears":
            # print("Dynamically added limb:", type)
            if not found_base:
                set_ears(2, side_arg=".l")
            else:
                # print("setting ears", '_dupli_' + dupli_id + '.l')
                set_ears(2, side_arg='_dupli_' + dupli_id + '.l', offset_arg=int(dupli_id))

        elif type == "tail":           
            if not found_base:
                print("set tail .x")
                set_tail(4, side_arg='.x')                
            else:
                print('set tail', '_dupli_' + dupli_id + '.x')
                set_tail(4, side_arg='_dupli_' + dupli_id + '.x')

        elif type == "breast":
            if not single_limb_exists:
                set_breast(True)

        elif type == "spline_ik":
            if not found_base:
                set_spline_ik(4, side_arg='.x')
            else:
                set_spline_ik(4, side_arg='_dupli_' + dupli_id + '.x')

        elif type == "bbones":
            if not found_base:
                set_bendy_bones(4, side_arg='.x')
            else:
                # print("create", '_dupli_' + dupli_id + '.x')
                set_bendy_bones(4, side_arg='_dupli_' + dupli_id + '.x')

        # Appended limbs
        elif not single_limb_exists:# check for single limbs, can be added only once

            addon_directory = os.path.dirname(os.path.abspath(__file__))
            filepath = addon_directory + "/armature_presets/modules.blend"

            # make a list of current custom shapes objects in the scene for removal later
            cs_objects = [obj.name for obj in bpy.data.objects if obj.name.startswith("cs_")]

            # load the objects in the file internal data
            with bpy.data.libraries.load(filepath, link=False) as (data_from, data_to):
                # only import the necessary armature
                data_to.objects = [i for i in data_from.objects if i == "rig_" + type]

            # check - is it in local view mode?
            # local view removed from Blender 2.8? Disable it for now
            """
            if context.space_data.lock_camera_and_layers == False:
                context.space_data.lock_camera_and_layers = True
                context.evaluated_depsgraph_get().update()
            """

            # link in scene
            for obj in data_to.objects:
                scene.collection.objects.link(obj)

            module_name = 'rig_' + type
            rig_module = get_object(module_name)
            
            if bpy.app.version >= (3,0,0):
                convert_drivers_cs_to_xyz(rig_module)
            
            rig_module.matrix_world = rig.matrix_world.copy()
            
            # global scale
            bone_scale_ref = 'c_pos'
            if rig.data.bones.get(bone_scale_ref):
                b_length = rig.data.bones[bone_scale_ref].length
                b_length *= rig.scale[0]
                rig_module.scale = [b_length / 0.22516] * 3
            else:
                print(bone_scale_ref, 'not found, scale is not set.')
                
                
            set_active_object(module_name)
            
            bpy.ops.object.mode_set(mode='POSE')
            
            # set default settings
            prefs = bpy.context.preferences.addons[__package__].preferences
            if type.startswith('arm.'):
                c_hand_ik_name = ard.arm_bones_dict["hand"]["control_ik"]
                c_hand_ik = get_pose_bone(c_hand_ik_name+side)        
                default_IKFK = prefs.default_ikfk_arm
                default_val = 0.0 if default_IKFK == 'IK' else 1.0
                set_prop_setting(c_hand_ik, 'ik_fk_switch', 'default', default_val)               
                c_hand_ik['ik_fk_switch'] = default_val

            elif type.startswith('leg.'):
                c_foot_ik_name = ard.leg_bones_dict['foot']['control_ik']
                c_foot_ik = get_pose_bone(c_foot_ik_name+side)        
                default_IKFK = prefs.default_ikfk_leg
                default_val = 0.0 if default_IKFK == 'IK' else 1.0
                set_prop_setting(c_foot_ik, 'ik_fk_switch', 'default', default_val)          
                c_foot_ik['ik_fk_switch'] = default_val
                
            elif type.startswith('head'):
                c_head_name = ard.heads_dict["control"]
                c_head = get_pose_bone(c_head_name)
                default_head_lock = prefs.default_head_lock
                default_val = 1 if default_head_lock else 0
                set_prop_setting(c_head, 'head_free', 'default', default_val)               
                c_head['head_free'] = default_val
            
            ref_bones_list = []# store the ref bones for selection            
            
            for b in rig_module.pose.bones:
                # replace custom shapes by custom shapes already existing in the scene
                if b.custom_shape:
                    if b.custom_shape.name not in cs_objects:
                        if b.custom_shape.name.replace('.001', '') in cs_objects:
                            b.custom_shape = get_object(b.custom_shape.name.replace('.001', ''))

                # handling of "dupli" naming
                new_dupli_side_x = '_dupli_' + dupli_id + '.x'
                if found_base:                    
                    b.name = b.name.split('.')[0] + '_dupli_' + dupli_id + '.' + b.name.split('.')[1]

                # store the ref bones for selection
                if '_ref_dupli_' in b.name or '_ref.' in b.name:
                    ref_bones_list.append(b.name)

                # retarget constraints
                if len(b.constraints) > 0:
                    for cns in b.constraints:
                        if 'target' in dir(cns):                       
                            if cns.target == None:
                                cns.target = get_object(rig_name)
                                
            # retarget drivers variables side
            if found_base:
                for dr in rig_module.animation_data.drivers:
                    if dr.data_path.startswith('pose.bones'):                       
                        for var in dr.driver.variables:
                            for tar in var.targets:
                                tar_pbname = get_pbone_name_from_data_path(tar.data_path)
                                tar_pbname_retarget = retarget_bone_side(tar_pbname, new_dupli_side_x, dupli_only=True)
                                tar.data_path = tar.data_path.replace(tar_pbname, tar_pbname_retarget)
                
                       

            # find added/useless custom shapes and delete them
            used_shapes = [b.custom_shape.name for b in rig_module.pose.bones if b.custom_shape]
            for obj in bpy.data.objects:
                if obj.name.startswith('cs_'):
                    if not obj.name in cs_objects and not obj.name in used_shapes:
                        delete_object(obj)

            bpy.ops.object.mode_set(mode='OBJECT')
            bpy.context.space_data.overlay.show_relationship_lines = False

            # add a transform offset to avoid duplis overlaps
            if found_base:
                offset = int(dupli_id)
                if offset:
                    #rig_module.location[0] += offset * 0.5
                    rig_module.location = rig_module.matrix_world @ Vector((offset*0.5, 0.0, 0.0))

            # Merge to the main armature
            bpy.ops.object.select_all(action='DESELECT')
            set_active_object(module_name)
            data_to_del = rig_module.data
            set_active_object(rig_name)
            bpy.ops.object.join()

            # delete remaining armature data in blend file
            bpy.data.armatures.remove(data_to_del, do_unlink=True)

            # Parent lost bones
            bpy.ops.object.mode_set(mode='EDIT')
            
            for bn in bpy.context.active_object.data.edit_bones:
                if len(bn.keys()) > 0:
                    if "arp_parent" in bn.keys():
                        parent_prop = get_edit_bone(bn["arp_parent"])
                        if bn.parent == None and parent_prop:
                            bn.parent = parent_prop

            # clean missing target of constraints
            bpy.ops.object.mode_set(mode='POSE')
            
            for pbone in bpy.context.active_object.pose.bones:
                if len(pbone.constraints) > 0:
                    for cns in pbone.constraints:
                        subtarget_string = ""
                        try:
                            subtarget_string = cns.subtarget
                        except:
                            pass
                        if subtarget_string != "":
                            subtarget = get_pose_bone(subtarget_string)
                            if not subtarget:
                                cns.subtarget = ""

            bpy.ops.object.mode_set(mode='EDIT')

            # select the ref bones
            rig.data.layers[17] = True
            layers_select = enable_all_armature_layers()

            bpy.ops.armature.select_all(action='DESELECT')
            
            for refname in ref_bones_list:                
                select_edit_bone(refname, mode=1)
            
            restore_armature_layers(layers_select)
            
            
            # Add the rig_add bones
            rig_add = get_rig_add(bpy.context.active_object)
            
            if rig_add:
                bones_added = []
                rig_add.hide_select = False
                unhide_object(rig_add)
                set_active_object(rig_add.name)
                bpy.ops.object.mode_set(mode='EDIT')
                bpy.ops.object.mode_set(mode='EDIT')
                blist = None
                if type[:-2] == "arm":
                    blist = ard.arm_bones_rig_add
                    print("found arm")
                if type[:-2] == "leg":
                    blist = ard.leg_bones_rig_add
                if type == "spine":
                    blist = ard.spine_bones_rig_add

                if blist:
                    for b in blist:
                        if type == "spine":
                            side_suff = ".x"
                            b_name = b[:-2]
                        else:
                            side_suff = '.' + type.split('.')[1]
                            b_name = b

                        new_bname = b_name + side_suff

                        if found_base:
                            new_bname = b_name + '_dupli_' + dupli_id + side_suff

                        if not get_edit_bone(new_bname):
                            newb = rig_add.data.edit_bones.new(new_bname)
                            newb.head, newb.tail = [0, 0, 0], [1, 1, 1]
                            bones_added.append(new_bname)
                            
                bpy.ops.object.mode_set(mode='OBJECT')
                bpy.ops.object.select_all(action='DESELECT')
                hide_object(rig_add)
                rig_add.hide_select = True
                set_active_object(rig_name)

    # Restore saved mode    
    restore_current_mode(current_mode)

    # Restore mirror
    rig.data.use_mirror_x = xmirror_state
    

def _export_data():
    print("Export transform constraints")

    # collect data in dict
    obj = bpy.context.active_object
    bpy.ops.object.mode_set(mode='POSE')
    dict = {}

    for pbone in obj.pose.bones:
        if len(pbone.constraints) > 0:
            for cns in pbone.constraints:
                if cns.type == 'TRANSFORM' and not 'eyelid_top' in pbone.name and not 'eyelid_bot' in pbone.name:  # eyelids constraints are handled when aligning facial
                    # dict format: constraint name, (constraint from min x, max x, min y...), bone length
                    name_base = pbone.name.replace('.l', '').replace('.r', '')
                    dict[name_base] = (cns.name), (
                        cns.from_min_x, cns.from_max_x, cns.from_min_y, cns.from_max_y, cns.from_min_z,
                        cns.from_max_z), pbone.length

    # save into file
    addon_directory = os.path.dirname(os.path.abspath(__file__))
    filepath = addon_directory + "/auto_rig_datas_export.py"

    file = open(filepath, "w", encoding="utf8", newline="\n")
    file.write(str(dict))
    file.close()


def _export_limbs(self):
    print("Export")
    
    rig = get_object(bpy.context.active_object.name)
    
    _initialize_armature(self)

    bpy.ops.armature.select_all(action='DESELECT')
    save_objects = []

    # Spine
    for b in ard.spine_bones + ard.spine_ref_list:
        if get_data_bone(b):# secondary are disabled by default
            select_edit_bone(b)

    bpy.ops.armature.separate()
    armature_name = "rig_spine"
    get_object("rig.001").name = armature_name
    save_objects.append(armature_name)

    # Head
    for b in ard.head_bones + ard.facial_bones + ard.facial_ref + ["head_ref.x", "neck_ref.x"] + ard.neck_bones:
        for side in [".l", ".r"]:
            suff = side
            if b.endswith(".x"):
                suff = ""
            if get_edit_bone(b + suff):
                select_edit_bone(b + suff)

    bpy.ops.armature.separate()    
    armature_name = "rig_head"
    get_object("rig.001").name = armature_name
    save_objects.append(armature_name)

    #   facial
    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.select_all(action='DESELECT')
    set_active_object("rig_head")
    duplicate_object()
    bpy.context.active_object.name = "rig_facial_temp"
    bpy.ops.object.mode_set(mode='EDIT')
    bpy.ops.armature.select_all(action='DESELECT')

    for bonename in (ard.facial_ref + ard.facial_bones):      
        sides = []
        if bonename.endswith(".x"):
            bonename = bonename.replace(".x", "")
            sides.append(".x")
        else:
            sides.append(".l")
            sides.append(".r")

        for side in sides:
            if get_edit_bone(bonename + side) == None:
                continue
            select_edit_bone(bonename + side)
            ebone = get_edit_bone(bonename + side)
            # save the external parent
            if ebone.parent:
                parent_list = ["c_skull_01.x", "c_skull_02.x", "c_skull_03.x", "head.x"]
                if ebone.parent:
                    if ebone.parent.name in parent_list:
                        ebone["arp_parent"] = ebone.parent.name

            # save the head matrix in a custom prop, used when appending it near to the head later
            if bonename == ard.facial_ref_dict['eyebrow_full']:                
                ebone["arp_offset_matrix"] = get_edit_bone(ard.head_ref[0]).matrix.inverted()

    bpy.ops.armature.separate()
    
    armature_name = "rig_facial"
    get_object("rig_facial_temp.001").name = armature_name
    save_objects.append(armature_name)

    delete_object(get_object("rig_facial_temp"))
    
    def export_facial_limb(armature_name, bones_list, matrix_bone_name, parent_list):
        bpy.ops.object.select_all(action='DESELECT')
        set_active_object("rig_head")
        duplicate_object()
        arm_name_temp = armature_name + "_temp"
        bpy.context.active_object.name = arm_name_temp
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.armature.select_all(action='DESELECT')
        
        for bonename in bones_list:
            if get_edit_bone(bonename) == None:
                continue
            select_edit_bone(bonename)
            ebone = get_edit_bone(bonename)
            
            # save the head matrix in a custom prop, used when appending it near to the head later
            if bonename == matrix_bone_name:
                ebone["arp_offset_matrix"] = get_edit_bone(ard.head_ref[0]).matrix.inverted()
                
            # save external parent
            if ebone.parent:        
                if ebone.parent:
                    if ebone.parent.name in parent_list:
                        ebone["arp_parent"] = ebone.parent.name
        
        bpy.ops.armature.separate()
        
        get_object(arm_name_temp+'.001').name = armature_name
        save_objects.append(armature_name)
        delete_object(get_object(arm_name_temp))
    
  
    export_facial_limb('rig_teeth', ard.teeth_bones+ard.teeth_ref, ard.teeth_bones[0], ['c_skull_01.x', 'jawbone.x'])
    export_facial_limb('rig_tongue', ard.tongue_bones+ard.tongue_ref, ard.tongue_bones[0], ['jawbone.x'])
    export_facial_limb('rig_mouth', ard.mouth_bones+ard.mouth_ref, ard.facial_ref_dict['jaw'], ['c_skull_01.x', 'c_skull_02.x', 'c_skull_03.x', 'head.x'])
    export_facial_limb('rig_chins', ard.chin_bones+ard.chin_ref, ard.chin_bones[0], ['jawbone.x'])
    export_facial_limb('rig_noses', ard.nose_bones+ard.nose_ref, ard.nose_bones[0], ['c_nose_01.x', 'c_skull_02.x'])
    export_facial_limb('rig_eye_l', ard.eye_bones_left, ard.eye_bones_left[0], ['head.x', 'c_skull_02.x', 'c_eye_target.x'])
    export_facial_limb('rig_eye_r', ard.eye_bones_right, ard.eye_bones_right[0], ['head.x', 'c_skull_02.x', 'c_eye_target.x'])
    export_facial_limb('rig_eyebrow_l', ard.eyebrow_bones_left, ard.eyebrow_bones_left[0], ['head.x', 'c_skull_02.x', 'c_skull_03.x'])
    export_facial_limb('rig_eyebrow_r', ard.eyebrow_bones_right, ard.eyebrow_bones_right[0], ['head.x', 'c_skull_02.x', 'c_skull_03.x'])
    export_facial_limb('rig_cheeks', ard.cheek_bones+ard.cheek_ref, ard.cheek_bones[0], ['head.x', 'c_skull_01.x', 'c_skull_02.x', 'c_skull_03.x'])
    
    
    bpy.ops.object.select_all(action='DESELECT')
    set_active_object(rig.name)
    bpy.ops.object.mode_set(mode='EDIT')

    # Arms
    for side in [".l", ".r"]:
        for b in ard.arm_bones + ard.arm_ref_list:
            if get_data_bone(b+side):# secondaries are disabled by default
                select_edit_bone(b+side)

        bpy.ops.armature.separate()
        
        armature_name = "rig_arm" + side
        get_object("rig.001").name = armature_name
        save_objects.append(armature_name)

    # Fingers
    # TODO: Substitute all of the same repeated list/operations by a single function
    bpy.ops.object.mode_set(mode='OBJECT')
    
    for side in [".l", ".r"]:        
        bpy.ops.object.select_all(action='DESELECT')
        set_active_object("rig_arm" + side)
        duplicate_object()
        bpy.context.active_object.name = "rig_fingers" + side
        bpy.ops.object.mode_set(mode='POSE')

        # clear inter-fingers constraints, otherwise the other finger is automatically appended when adding the limb
        get_pose_bone("c_middle1_base" + side).constraints["Copy Rotation"].target = None
        get_pose_bone("c_ring1_base" + side).constraints["Copy Rotation"].target = None

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

        # thumb
        for b in ard.thumb_ref_list + ard.thumb_control_list + ard.thumb_intern_list:
            select_edit_bone(b + side)

        #   save the hand matrix in a custom prop, used when appending it near to the hand later
        get_edit_bone(ard.thumb_ref_list[0] + side)["arp_offset_matrix"] = get_edit_bone(
            "hand_ref" + side).matrix.inverted()

        bpy.ops.armature.separate()
        armature_name = "rig_thumb" + side
        get_object("rig_fingers" + side + ".001").name = armature_name
        save_objects.append(armature_name)

        # index
        for b in ard.index_ref_list + ard.index_control_list + ard.index_intern_list:
            select_edit_bone(b + side)

        #   save the hand matrix in a custom prop, used when appending it near to the hand later
        get_edit_bone(ard.index_ref_list[0] + side)["arp_offset_matrix"] = get_edit_bone(
            "hand_ref" + side).matrix.inverted()

        bpy.ops.armature.separate()
        armature_name = "rig_index" + side
        get_object("rig_fingers" + side + ".001").name = armature_name
        save_objects.append(armature_name)

        # middle
        for b in ard.middle_ref_list + ard.middle_control_list + ard.middle_intern_list:
            select_edit_bone(b + side)

        #   save the hand matrix in a custom prop, used when appending it near to the hand later
        get_edit_bone(ard.middle_ref_list[0] + side)["arp_offset_matrix"] = get_edit_bone("hand_ref" + side).matrix.inverted()

        bpy.ops.armature.separate()
        armature_name = "rig_middle" + side
        get_object("rig_fingers" + side + ".001").name = armature_name
        save_objects.append(armature_name)

        # ring
        for b in ard.ring_ref_list + ard.ring_control_list + ard.ring_intern_list:
            select_edit_bone(b + side)

        #   save the hand matrix in a custom prop, used when appending it near to the hand later
        get_edit_bone(ard.ring_ref_list[0] + side)["arp_offset_matrix"] = get_edit_bone(
            "hand_ref" + side).matrix.inverted()

        bpy.ops.armature.separate()
        armature_name = "rig_ring" + side
        get_object("rig_fingers" + side + ".001").name = armature_name
        save_objects.append(armature_name)

        # pinky
        for b in ard.pinky_ref_list + ard.pinky_control_list + ard.pinky_intern_list:
            select_edit_bone(b + side)

        #   save the hand matrix in a custom prop, used when appending it near to the hand later
        get_edit_bone(ard.pinky_ref_list[0] + side)["arp_offset_matrix"] = get_edit_bone("hand_ref" + side).matrix.inverted()

        bpy.ops.armature.separate()
        armature_name = "rig_pinky" + side
        get_object("rig_fingers" + side + ".001").name = armature_name
        save_objects.append(armature_name)

        delete_object(get_object("rig_fingers" + side))
        
    bpy.ops.object.select_all(action='DESELECT')
    set_active_object(rig.name)
    bpy.ops.object.mode_set(mode='EDIT')

    # Legs
    for side in [".l", ".r"]:
        for b in ard.leg_bones_list + ard.leg_ref_bones_list:
            if get_edit_bone(b + side) == None:
                continue

            select_edit_bone(b + side)

        bpy.ops.armature.separate()
        armature_name = "rig_leg" + side
        get_object("rig.001").name = armature_name
        save_objects.append(armature_name)

    # Toes
    # TODO: Substitute all of the same repeated list/operations by a single algorithm
    bpy.ops.object.mode_set(mode='OBJECT')
    
    for side in [".l", ".r"]:        
        bpy.ops.object.select_all(action='DESELECT')
        set_active_object("rig_leg" + side)
        duplicate_object()
        bpy.context.active_object.name = "rig_toes" + side
        bpy.ops.object.mode_set(mode='POSE')

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

        # thumb
        for b in ard.toes_thumb_ref_list + ard.toes_thumb_control_list:
            select_edit_bone(b + side)

            # save the foot(toes_ref) matrix in a custom prop, used when appending it near to the hand later
        get_edit_bone(ard.toes_thumb_ref_list[0] + side)["arp_offset_matrix"] = get_edit_bone("toes_ref" + side).matrix.inverted()

        bpy.ops.armature.separate()
        armature_name = "rig_toes_thumb" + side
        get_object("rig_toes" + side + ".001").name = armature_name
        save_objects.append(armature_name)

        # index
        for b in ard.toes_index_ref_list + ard.toes_index_control_list:
            select_edit_bone(b + side)

            # save the hand matrix in a custom prop, used when appending it near to the hand later
        get_edit_bone(ard.toes_index_ref_list[0] + side)["arp_offset_matrix"] = get_edit_bone(
            "toes_ref" + side).matrix.inverted()

        bpy.ops.armature.separate()
        armature_name = "rig_toes_index" + side
        get_object("rig_toes" + side + ".001").name = armature_name
        save_objects.append(armature_name)

        # middle
        for b in ard.toes_middle_ref_list + ard.toes_middle_control_list:
            select_edit_bone(b + side)

            # save the hand matrix in a custom prop, used when appending it near to the hand later
        get_edit_bone(ard.toes_middle_ref_list[0] + side)["arp_offset_matrix"] = get_edit_bone(
            "toes_ref" + side).matrix.inverted()

        bpy.ops.armature.separate()
        armature_name = "rig_toes_middle" + side
        get_object("rig_toes" + side + ".001").name = armature_name
        save_objects.append(armature_name)

        # ring
        for b in ard.toes_ring_ref_list + ard.toes_ring_control_list:
            select_edit_bone(b + side)

            # save the hand matrix in a custom prop, used when appending it near to the hand later
        get_edit_bone(ard.toes_ring_ref_list[0] + side)["arp_offset_matrix"] = get_edit_bone(
            "toes_ref" + side).matrix.inverted()

        bpy.ops.armature.separate()
        armature_name = "rig_toes_ring" + side
        get_object("rig_toes" + side + ".001").name = armature_name
        save_objects.append(armature_name)

        # pinky
        for b in ard.toes_pinky_ref_list + ard.toes_pinky_control_list:
            select_edit_bone(b + side)

            # save the hand matrix in a custom prop, used when appending it near to the hand later
        get_edit_bone(ard.toes_pinky_ref_list[0] + side)["arp_offset_matrix"] = get_edit_bone(
            "toes_ref" + side).matrix.inverted()

        bpy.ops.armature.separate()
        armature_name = "rig_toes_pinky" + side
        get_object("rig_toes" + side + ".001").name = armature_name
        save_objects.append(armature_name)

        delete_object(get_object("rig_toes" + side))
        
    bpy.ops.object.select_all(action='DESELECT')
    set_active_object(rig.name)
    bpy.ops.object.mode_set(mode='EDIT')

    # Delete other objects
    for obj in bpy.data.objects:
        if not obj.name in save_objects and obj.name[:3] != "cs_":
            delete_object(obj)

    # Delete any constraints dependency remaining between armatures
    for obj in bpy.data.objects:
        if obj.type == "ARMATURE":
            set_active_object(obj.name)
            bpy.ops.object.mode_set(mode='POSE')
            for b in obj.pose.bones:
                for cns in b.constraints:
                    try:
                        if obj.name != "rig_spine":
                            if cns.target == get_object("rig_spine"):
                                cns.target = None
                    except:
                        pass

            # Delete invalid drivers from older dependencies path
            remove_invalid_drivers()

    # save the file
    print("")
    
    fp = bpy.data.filepath
    addon_directory = os.path.dirname(fp)#os.path.dirname(os.path.abspath(__file__))    
    fp = addon_directory + "\modules.blend"
    print("saving modules to path:", fp)
    bpy.ops.wm.save_as_mainfile(filepath=fp, copy=True)
    
    # end export_limbs()
    
    
def _set_character_name(self):
    # rig object name
    rig = bpy.context.active_object
    rig.name = self.name + "_rig"

    # rig_add object name
    rig_add = get_rig_add(rig)
    if rig_add:
        rig_add.name = self.name + "_rig_add"

    # empty groups object names
    if rig.parent:
        rig.parent.name = self.name + "_grp"
        if rig.parent.parent:
            rig.parent.name = self.name + "1_grp"
            rig.parent.parent.name = self.name + "_grp"
            
    
    # collection names
    col_rig = get_rig_collection(rig)
    col_master = get_master_collection(col_rig)
    col_cs = get_cs_collection(col_master)
    
    if col_rig:
        col_rig.name = self.name + "_grp_rig"
        
    if col_master:     
        col_master.name = self.name            

    if col_cs:      
        col_cs.name = self.name + "_cs"

        

def _import_picker(filepath, self, context):
    scn = bpy.context.scene

    file = open(filepath, 'rU')
    file_lines = file.readlines()
    dict_string = str(file_lines[0])
    file.close()

    dict_bones = ast.literal_eval(dict_string)

    # Disable picker
    proxy_picker_state = False

    if len(scn.keys()) > 0:
        if "Proxy_Picker" in scn.keys():
            proxy_picker_state = scn.Proxy_Picker.active
            scn.Proxy_Picker.active = False

    # Save current mode
    current_mode = get_current_mode()

    bpy.ops.object.mode_set(mode='EDIT')

    # Save X-Mirror state
    xmirror_state = bpy.context.object.data.use_mirror_x
    bpy.context.object.data.use_mirror_x = False

    # Add the picker bones if not there
    addon_directory = os.path.dirname(os.path.abspath(__file__))
    base_filepath = addon_directory + "/picker.py"

    if bpy.context.active_object.data.edit_bones.get("Picker") == None:
        _add_picker(self, context, base_filepath, False, True)

    print("Import bones position...")

    for b in dict_bones:
        if bpy.context.active_object.data.edit_bones.get(b):
            ebone = get_edit_bone(b)
            ebone.head, ebone.tail, ebone.roll = dict_bones[b][0], dict_bones[b][1], dict_bones[b][2]

            pbone = get_pose_bone(b)

            if len(dict_bones[b][3]) > 0:
                for prop in dict_bones[b][3]:
                    pbone[prop[0]] = prop[1]

        else:
            print(b, "not found in the selected armature, picker bones datas skipped.")

            # Add the picker background
            # Delete the current objects
    for child in bpy.context.active_object.children:
        if "rig_ui" in child.name and child.type == "EMPTY":
            delete_children(child, "OBJECT")
            break

    _add_picker(self, context, filepath, True, False)

    # Restore X-Mirror state
    bpy.context.object.data.use_mirror_x = xmirror_state

    # Restore picker state
    if len(scn.keys()) > 0:
        if "Proxy_Picker" in scn.keys():
            scn.Proxy_Picker.active = proxy_picker_state

    # Restore saved mode
    restore_current_mode(current_mode)


def _export_picker(filepath, self, context):
    scn = bpy.context.scene

    if bpy.context.active_object.data.bones.get("Picker"):

        # Add extension to file name
        if filepath[-3:] != ".py":
            filepath += ".py"

        file = open(filepath, "w", encoding="utf8", newline="\n")
        dict = {}

        # Save current mode
        current_mode = bpy.context.mode
        bpy.ops.object.mode_set(mode='EDIT')

        # Get picker bones

        # Save displayed layers
        _layers = [bpy.context.active_object.data.layers[i] for i in range(0, 32)]

        # Display all layers
        for i in range(0, 32):
            bpy.context.active_object.data.layers[i] = True

        # Select picker children bones
        bpy.ops.armature.select_all(action='DESELECT')
        bpy.context.evaluated_depsgraph_get().update()
        bpy.context.active_object.data.edit_bones.active = get_edit_bone("Picker")
        bpy.ops.armature.select_similar(type='CHILDREN')

        picker_bones = [ebone for ebone in bpy.context.active_object.data.edit_bones if ebone.select]

        def listify(vector):
            return [vector[0], vector[1], vector[2]]

        # Write bones datas
        for b in picker_bones:

            bone_grp = ""
            bone_shape = ""
            pbone = get_pose_bone(b.name)
            prop_list = []

            if len(pbone.keys()) > 0:
                for i in pbone.keys():
                    if type(pbone[i]) is float or type(pbone[i]) is int or type(pbone[i]) is str:
                        prop_list.append([i, pbone[i]])

            if pbone.bone_group:
                bone_grp = pbone.bone_group.name
            if pbone.custom_shape:
                bone_shape = pbone.custom_shape.name

            # dict: 0 head, 1 tail, 2 roll, 3 properties,  4 bone group, 5 bone shape
            dict[b.name] = [listify(b.head), listify(b.tail), b.roll, prop_list, bone_grp, bone_shape]

        file.write(str(dict))

        # Write object datas
        if bpy.data.objects.get("rig_ui"):
            obj_dict = {}
            for obj in bpy.data.objects["rig_ui"].children:
                mesh_datas = None
                text_datas = []
                empty_datas = []

                if obj.type == "MESH":
                    vert_list = [(v.co[0], v.co[1], v.co[2]) for v in obj.data.vertices]
                    edge_list = [(e.vertices[0], e.vertices[1]) for e in obj.data.edges]
                    poly_list = []
                    for p in obj.data.polygons:
                        v_list = [v for v in p.vertices]
                        poly_list.append(tuple(v_list))
                    mesh_datas = (vert_list, edge_list, poly_list)

                if obj.type == "FONT":
                    font_text = obj.data.body
                    font_size = obj.data.size
                    font_align = obj.data.align_x
                    text_datas = (font_text, font_size, font_align)

                if obj.type == "EMPTY":
                    empty_type = obj.empty_display_type
                    empty_img_offset = [obj.empty_image_offset[0], obj.empty_image_offset[1]]
                    empty_img_path = ""
                    try:
                        empty_img_path = obj.data.filepath
                    except:
                        pass
                    empty_datas = [empty_type, empty_img_offset, empty_img_path]

                # dict: 0 loc, 1 rot, 2 scale, 3 type, 4 mesh datas, 5 text datas, 6 empty datas
                obj_dict[obj.name] = [[obj.location[0], obj.location[1], obj.location[2]],
                                      [obj.rotation_euler[0], obj.rotation_euler[1], obj.rotation_euler[2]],
                                      [obj.scale[0], obj.scale[1], obj.scale[2]], obj.type, mesh_datas, text_datas,
                                      empty_datas]

            file.write("\n" + str(obj_dict))

            # Close file
        file.close()

        # Restore layers
        for i in range(0, 32):
            bpy.context.active_object.data.layers[i] = _layers[i]

        # Restore saved mode
        restore_current_mode(current_mode)

        print("Picker saved")

    else:
        self.report({"ERROR"}, "No picker found")


def _add_picker(self, context, filepath, with_background, with_bones):
    print("\nGenerating picker panel...")

    scn = bpy.context.scene
    picker_generated = False

    file = open(filepath, 'rU')
    print("Importing from file:", filepath)
    file_lines = file.readlines()
    dict_bones_string = str(file_lines[0])
    dict_obj_string = str(file_lines[1])
    file.close()

    dict_bones = ast.literal_eval(dict_bones_string)
    dict_obj = ast.literal_eval(dict_obj_string)

    # Disable picker
    proxy_picker_state = False

    if len(scn.keys()) > 0:
        proxy_picker_is_valid = True
        try:
            scn.Proxy_Picker
        except:
            proxy_picker_is_valid = False

        if proxy_picker_is_valid:
            proxy_picker_state = scn.Proxy_Picker.active
            scn.Proxy_Picker.active = False

    # Save current mode
    current_mode = bpy.context.mode
    bpy.ops.object.mode_set(mode='EDIT')

    # Save X-Mirror state
    xmirror_state = bpy.context.object.data.use_mirror_x
    bpy.context.object.data.use_mirror_x = False

    if with_bones:
        if bpy.context.active_object.data.bones.get("Picker") == None:
            print("Adding picker bones...")

            # Create the main picker group bone
            print("Create the 'Picker' bone")
            pickerb = bpy.context.active_object.data.edit_bones.new("Picker")
            pickerb.head = [0, 0, 0]
            pickerb.tail = [0, 0, 1]
            pickerb.use_deform = False
            bpy.ops.object.mode_set(mode='POSE')
            bpy.ops.object.mode_set(mode='EDIT')

            # get the limbs
            limb_sides.get_multi_limbs()

            # Set layers
            get_edit_bone("Picker").layers[23] = True
            for i, l in enumerate(get_edit_bone("Picker").layers):
                if i != 23:
                    get_edit_bone("Picker").layers[i] = False

            # Save displayed layers
            _layers = [bpy.context.active_object.data.layers[i] for i in range(0, 32)]

            # Display all layers
            for i in range(0, 32):
                bpy.context.active_object.data.layers[i] = True

            print("Create bones...")
            bones_to_append = ['c_pos_proxy', 'c_traj_proxy', 'layer_disp_main', 'layer_disp_second']

            # morph buttons
            for b in dict_bones:
                if 'c_morph_' in b or b.startswith('c_pupil') or b.startswith('c_iris'):
                    bones_to_append.append(b)

            # legs
            for side in limb_sides.leg_sides:
                if not "dupli" in side:
                    for leg_bone_cont in ard.leg_control:
                        bname = leg_bone_cont + side
                        if get_edit_bone(bname):
                            bones_to_append.append(leg_bone_cont + '_proxy' + side)

            # arms
            for side in limb_sides.arm_sides:
                if not "dupli" in side:
                    for arm_bone_cont in ard.arm_control + ard.fingers_control:
                        bname = arm_bone_cont + side
                        if get_edit_bone(bname):
                            bones_to_append.append(arm_bone_cont + '_proxy' + side)

            # spine
            for side in limb_sides.spine_sides:
                if not "dupli" in side:
                    for spine_bone_cont in ard.spine_control:
                        bname = spine_bone_cont[:-2] + side
                        if get_edit_bone(bname):
                            if "_proxy" in spine_bone_cont:
                                continue
                            bones_to_append.append(spine_bone_cont[:-2] + '_proxy' + side)                            

            # neck
            for neck_bone_cont in ard.neck_control:
                bname = neck_bone_cont
                if get_edit_bone(bname):
                    bones_to_append.append(bname[:-2] + '_proxy.x')                    

            # head
            for side in limb_sides.head_sides:
                if not "dupli" in side:
                    for head_bone_cont in ard.head_control + ard.facial_control:
                        bname = ''
                        proxy_name = ''

                        if '.x' in head_bone_cont:
                            bname = head_bone_cont[:-2] + side
                            proxy_name = head_bone_cont[:-2] + '_proxy'
                            
                            if get_edit_bone(bname): 
                                bones_to_append.append(proxy_name + side)
                        else:
                            for s in ['.l', '.r']:
                                bname = head_bone_cont + s
                                proxy_name = head_bone_cont + '_proxy'
                                if get_edit_bone(bname):
                                    bones_to_append.append(proxy_name + s)

            for b in bones_to_append:
                bpy.ops.object.mode_set(mode='EDIT')
                ebone = get_edit_bone(b)
               
                if ebone == None:
                    # create a new bone
                    ebone = bpy.context.active_object.data.edit_bones.new(b)

                ebone.parent = get_edit_bone("Picker")

                # Set transforms
                ebone.head, ebone.tail, ebone.roll = dict_bones[b][0], dict_bones[b][1], dict_bones[b][2]
                ebone.use_deform = False

                # Set properties and shapes
                bpy.ops.object.mode_set(mode='POSE')
                pbone = get_pose_bone(b)

                if len(dict_bones[b][3]) > 0:
                    for prop in dict_bones[b][3]:
                        pbone[prop[0]] = prop[1]

                # Old old file retro-compatibility -Check the custom shape is in the scene, otherwise append it from the template file
                if len(pbone.keys()) > 0:
                    if "normal_shape" in pbone.keys():
                        if bpy.data.objects.get(pbone["normal_shape"]) == None:
                            obj_to_append = [pbone["normal_shape"]]  # , pbone["normal_shape"] + "_sel"]
                            append_from_arp(nodes=obj_to_append, type="object")
                            print("Appended custom shape:", obj_to_append)

                # Custom shape
                cs = get_object(dict_bones[b][5])
                if cs:
                    pbone.custom_shape = cs

                    # eyebrows have larger scale
                    if "c_eyebrow_full" in pbone.name:
                        set_custom_shape_scale(pbone, 4.0)

                # Fix the reset button since there is no arp_layer assign
                if "c_picker_reset" in pbone.name:
                    pbone["arp_layer"] = 16

                    # Check if the reset script is in scene, otherwise append it
                    if "button" in pbone.keys():
                        if bpy.data.texts.get(pbone["button"]) == None:
                            append_from_arp(nodes=pbone["button"], type="text")

                # Set layers              
                if len(pbone.keys()):                
                    if "proxy" in pbone.keys():
                        if get_pose_bone(pbone["proxy"]):
                            proxy_bone = get_pose_bone(pbone["proxy"])

                            for i, l in enumerate(pbone.bone.layers):
                                pbone.bone.layers[i] = proxy_bone.bone.layers[i]
                                
                        else:# no target bone found, set in layer 1 by default
                            print("Set bone default layer", pbone.name)
                            set_bone_layer(pbone.bone, 1)
                            

                    if "arp_layer" in pbone.keys():
                        set_layer_idx = pbone["arp_layer"]
                        set_bone_layer(pbone.bone, set_layer_idx)
                        # buttons must be in the two layers
                        if pbone.name == "layer_disp_main" or pbone.name == "layer_disp_second":
                            set_bone_layer(pbone.bone, 1, multi=True)


                # Set group colors
                try:
                    pbone.bone_group = bpy.context.active_object.pose.bone_groups[dict_bones[b][4]]
                except:
                    print('Bone group "body ' + dict_bones[b][4] + ' not found')

                    # set limit constraints for morph buttons
                if b.startswith("c_morph_") or b.startswith("c_pupil") or b.startswith("c_iris"):
                    cns = pbone.constraints.get("Limit Location")
                    if cns == None:
                        cns = pbone.constraints.new("LIMIT_LOCATION")
                        cns.owner_space = "LOCAL"
                        cns.use_min_x = cns.use_max_x = cns.use_min_y = cns.use_max_y = True
                        cns.use_transform_limit = True

            bpy.ops.object.mode_set(mode='EDIT')
            _set_picker_spine()

            # Multi limb support
            multi_limb_support = True

            if multi_limb_support:
                multi_ref_list = []
                dupli_list = ["shoulder_ref_dupli", "thigh_ref_dupli", "thumb1_ref_dupli", "index1_ref_dupli",
                              "middle1_ref_dupli", "ring1_ref_dupli", "pinky1_ref_dupli"]
                bpy.ops.object.mode_set(mode='EDIT')

                for bone in bpy.context.active_object.data.edit_bones:
                    for b in dupli_list:
                        if b in bone.name:
                            multi_ref_list.append(bone.name)

                # Duplicate picker bones
                for multi in multi_ref_list:
                    
                    side = multi[-2:]
                    suffix = multi.split("_dupli_")[1][:-2]

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

                    # Select
                    current_limb = None
                    if "shoulder" in multi:
                        current_limb = ard.arm_control
                    if "thigh" in multi:
                        current_limb = ard.leg_control

                    fingers = ["thumb", "index", "middle", "ring", "pinky"]
                    for finger in fingers:

                        if finger in multi and not "toes_" in multi:
                            current_limb = ["c_" + finger + "1_base", "c_" + finger + "1", 'c_' + finger + "2",
                                            'c_' + finger + "3"]
                            break

                        if finger in multi and "toes_" in multi:
                            current_limb = ["c_toes_" + finger + "1_base", "c_toes_" + finger + "1",
                                            'c_toes_' + finger + "2", 'c_toes_' + finger + "3"]
                            break

                    for bone1 in current_limb:
                        if get_edit_bone(bone1r+ '_proxy' + side):
                            proxy_bone = get_edit_bone(bone1 + '_proxy' + side)
                            if proxy_bone.layers[22] == False:  # if not disabled (finger, toes...)
                                proxy_bone.select = True

                    bpy.ops.object.mode_set(mode='POSE')
                    bpy.ops.object.mode_set(mode='EDIT')  # debug selection

                    coef = 1
                    if side == '.r':
                        coef = -1
                    suffix_count = int(float(suffix))  # move offset for each dupli, get number of the limb

                    duplicate(type="EDIT_BONE")

                    # Move
                    for bone in get_selected_edit_bones():
                        move_bone(bone.name, 0.26 * coef * suffix_count, 0)

                    # Rename
                    for bone in get_selected_edit_bones():
                        bone.name = bone.name[:-4].replace(side, '_dupli_' + suffix + side)
                        # Set proxy bone
                        get_pose_bone(bone.name)['proxy'] = get_pose_bone(bone.name)['proxy'].replace(side,
                                                                                                      '_dupli_' + suffix + side)

            # Restore layers
            for i in range(0, 32):
                bpy.context.active_object.data.layers[i] = _layers[i]

            picker_generated = True

        else:
            print("Picker bones already loaded, nothing to load.")

    if with_background:
        rig_ui = None
        rig_collecs = [col for col in bpy.context.active_object.users_collection]

        for child in bpy.context.active_object.children:
            if "rig_ui" in child.name:
                rig_ui = child
                print("Found rig_ui")

        if rig_ui == None:
            print("Adding picker objects...")
            rig_ui = bpy.data.objects.new("rig_ui", None)
            # scn.collection.objects.link(rig_ui)
            for col in rig_collecs:
                col.objects.link(rig_ui)
            rig_ui.empty_display_size = 0.01
            rig_ui.parent = bpy.context.active_object
            rig_ui.hide_select = True

            # Meshes and text objects
            for obj in dict_obj:
                # do not import ik-fk texts for now... requires drivers
                if not "label_ik" in obj and not "label_fk" in obj:

                    if dict_obj[obj][3] == "MESH":
                        new_mesh = bpy.data.meshes.new(obj)
                        new_obj = bpy.data.objects.new(obj, new_mesh)
                        for col in rig_collecs:
                            col.objects.link(new_obj)
                        # scn.collection.objects.link(new_obj)
                        # create verts and faces
                        mesh_datas = dict_obj[obj][4]
                        new_mesh.from_pydata(mesh_datas[0], mesh_datas[1], mesh_datas[2])

                        # set transforms
                        new_obj.location = dict_obj[obj][0]
                        new_obj.rotation_euler = dict_obj[obj][1]
                        new_obj.scale = dict_obj[obj][2]

                        # assign mat
                        mat_ui = None

                        for mat in bpy.data.materials:
                            if "cs_ui" in mat.name:
                                mat_ui = mat
                                break

                        if mat_ui == None:
                            mat_ui = bpy.data.materials.new("cs_ui")
                            mat_ui.diffuse_color = (0.2, 0.2, 0.2, 1.0)

                        if mat_ui:
                            new_obj.data.materials.append(mat_ui)
                        else:
                            print("UI material 'cs_ui' not found.")

                    if dict_obj[obj][3] == "FONT":

                        new_font = bpy.data.curves.new(obj + "_font", 'FONT')
                        new_text = bpy.data.objects.new(obj, new_font)
                        for col in rig_collecs:
                            col.objects.link(new_text)
                        # scn.collection.objects.link(new_text)
                        # set transforms
                        new_text.location = dict_obj[obj][0]
                        new_text.rotation_euler = dict_obj[obj][1]
                        new_text.scale = dict_obj[obj][2]

                        # text values
                        new_text.data.body = dict_obj[obj][5][0]
                        new_text.data.size = dict_obj[obj][5][1]
                        new_text.data.align_x = dict_obj[obj][5][2]

                        # assign mat
                        mat_text = None

                        for mat in bpy.data.materials:
                            if "cs_text" in mat.name:
                                mat_text = mat
                                break

                        if mat_text == None:
                            mat_text = bpy.data.materials.new("cs_text")
                            mat_text.diffuse_color = (0.88, 0.88, 0.88, 1.0)

                        if mat_text:
                            new_text.data.materials.append(mat_text)
                        else:
                            print("Text material 'cs_text' not found.")

                        # assign font
                        fnt = None

                        for f in bpy.data.fonts:
                            if "MyriadPro-Bold" in f.name:
                                fnt = f

                        if not fnt:
                            append_from_arp(["MyriadPro-Bold"], "font")
                        if fnt:
                            new_text.data.font = fnt

                    if dict_obj[obj][3] == "EMPTY":
                        _draw_type = dict_obj[obj][6][0]
                        _image_offset = dict_obj[obj][6][1]
                        _img_path = dict_obj[obj][6][2]

                        new_emp = bpy.data.objects.new(obj, None)
                        new_emp.empty_display_type = _draw_type
                        new_emp.empty_image_offset = _image_offset

                        # load image
                        if _img_path != "":
                            try:
                                img = bpy.data.images.load(_img_path)
                                new_emp.data = img
                            except:
                                print("Cannot load image path")

                        for col in rig_collecs:
                            col.objects.link(new_emp)

                        # scn.collection.objects.link(new_emp)
                        # set transforms
                        new_emp.location = dict_obj[obj][0]
                        new_emp.rotation_euler = dict_obj[obj][1]
                        new_emp.scale = dict_obj[obj][2]

                    bpy.data.objects[obj].parent = rig_ui
                    bpy.data.objects[obj].hide_select = True

            picker_generated = True

        else:
            print("Picker background already loaded, nothing to load.")

    # Restore X-Mirror state
    bpy.context.object.data.use_mirror_x = xmirror_state

    # Restore picker state
    if len(scn.keys()) > 0:
        proxy_picker_is_valid = True
        try:
            scn.Proxy_Picker
        except:
            proxy_picker_is_valid = False

        if proxy_picker_is_valid:
            scn.Proxy_Picker.active = proxy_picker_state

    # Restore saved mode
    restore_current_mode(current_mode)

    print("Picker loading finished.")

    # has the picker been generated?
    return picker_generated


def _set_picker_spine():
    rig = get_object(bpy.context.active_object.name)
    addon_directory = os.path.dirname(os.path.abspath(__file__))
    filepath = addon_directory + "/picker.py"
    file = open(filepath, 'rU')
    file_lines = file.readlines()
    dict_bones_string = str(file_lines[0])
    dict_bones = ast.literal_eval(dict_bones_string)
    # dict_obj_string = str(file_lines[1])
    file.close()

    dict_bones = ast.literal_eval(dict_bones_string)

    # count current spine bones
    total_spine_found = 1
    for idx in range(1, 64):
        id = '%02d' % idx
        spine_ref = get_data_bone('spine_' + id + '_ref.x')
        if spine_ref:
            total_spine_found += 1
            
    master_found = get_data_bone('c_spine_master.x') != None
    
    # create additional picker spine bones
    root_master_name = 'c_root_master_proxy.x'
    root_name = 'c_root_proxy.x'
    root_bend_name = 'c_root_bend_proxy.x'
    first_spine_name = 'c_spine_01_proxy.x'
    first_spine_bend_name = 'c_spine_01_bend_proxy.x'
    waist_bend_name = 'c_waist_bend_proxy.x'
    second_spine_name = 'c_spine_02_proxy.x'
    second_spine_bend_name = 'c_spine_02_bend_proxy.x'
    third_spine_name = 'c_spine_03_proxy.x'

    first_spine = get_edit_bone(first_spine_name)
    first_spine_bend = get_edit_bone(first_spine_bend_name)
    waist_bend = get_edit_bone(waist_bend_name)
    second_spine = get_edit_bone(second_spine_name)
    second_spine_bend = get_edit_bone(second_spine_bend_name)
    third_spine = get_edit_bone(third_spine_name)
    
    # create
    bones_to_create = []

    if total_spine_found >= 1:
        if first_spine == None:
            bones_to_create.append(first_spine_name)
        if first_spine_bend == None:
            bones_to_create.append(first_spine_bend_name)
        if waist_bend == None:
            bones_to_create.append(waist_bend_name)
            
            
    if master_found:
        picker_parent_bone = get_edit_bone("Picker")
        if picker_parent_bone:
            master_pic_name = 'c_spine_master_proxy.x'
            master_eb = create_edit_bone(master_pic_name)
            master_eb.parent = picker_parent_bone
            z_max = -56.2491
            z_min = -56.4268            
            master_eb.head[0] = 0.0
            master_eb.head[1] = -0.508136
            master_eb.head[2] = z_max
            master_eb.tail = master_eb.head + Vector((0.0, 0.0, 0.01))
            master_eb.roll = 0.0
            
            # Set properties and shapes
            bpy.ops.object.mode_set(mode='POSE')
            
            master_pb = get_pose_bone(master_pic_name)
            cs_name = 'cs_solid_star'
            set_bone_custom_shape(master_pb, cs_name)            
            master_pb['normal_shape'] = cs_name
            master_pb['proxy'] = 'c_spine_master.x'
            master_pb['select_shape'] = ''       
            
            # Set layers     
            set_bone_layer(master_pb.bone, 0)
            
            # Set group colors
            set_bone_color_group(rig, master_pb, 'body.x')
            
            bpy.ops.object.mode_set(mode='EDIT')
            
    
    if total_spine_found >= 2:
        bpy.ops.object.mode_set(mode='EDIT')        
        
        picker_parent_bone = get_edit_bone("Picker")
        
        if picker_parent_bone:
            # remove unnecessary
            
            for spine_idx in range (total_spine_found, 32):
                idx = '%02d' % spine_idx
                spine_pic_name = 'c_spine_' + idx + '_proxy.x'
                spine_eb = get_edit_bone(spine_pic_name)
                if spine_eb:
                    delete_edit_bone(spine_eb)
                    
                spine_bend_name = 'c_spine_' + idx + '_bend_proxy.x'
                spine_bend_eb = get_edit_bone(spine_bend_name)
                if spine_bend_eb:
                    delete_edit_bone(spine_bend_eb)
                    
            
            for spine_idx in range (2, total_spine_found):
                picker_parent_bone = get_edit_bone("Picker")
                
                idx = '%02d' % spine_idx
                # add main spine
                spine_pic_name = 'c_spine_' + idx + '_proxy.x'
                spine_eb = create_edit_bone(spine_pic_name)
                spine_eb.parent = picker_parent_bone
                
                # set coords
                z_max = -56.2491
                z_min = -56.4268
                
                spine_eb.head[0] = 0.0
                spine_eb.head[1] = -0.508136
                spine_eb.head[2] = z_min + (z_max-z_min) * (spine_idx-1)/(total_spine_found-1)

                spine_eb.tail = spine_eb.head + Vector((0.0, 0.0, 0.01))
                spine_eb.roll = 0.0
                
                # Set properties and shapes
                bpy.ops.object.mode_set(mode='POSE')
                
                spine_pb = get_pose_bone(spine_pic_name)
                cs_name = 'cs_solid_bar_01'
                spine_pb['normal_shape'] = cs_name
                spine_pb['proxy'] = 'c_spine_' + idx + '.x'
                spine_pb['select_shape'] = 'cs_solid_bar_01_sel'
                
                # Custom shape                
                spine_pb.custom_shape = get_object(cs_name)
                
                # Set layers     
                set_bone_layer(spine_pb.bone, 0)
                
                # Set group colors
                set_bone_color_group(rig, spine_pb, 'body.x')
                
                bpy.ops.object.mode_set(mode='EDIT')
                
                # add spine_bend 
                if rig.arp_secondary_type != 'NONE':
                    spine_bend_name = 'c_spine_' + idx + '_bend_proxy.x'
                    spine_bend_eb = create_edit_bone(spine_bend_name)
                    spine_bend_eb.parent = picker_parent_bone
                   
                    # set coords
                    z_max = -56.2491
                    z_min = -56.4268
                    
                    spine_bend_eb.head[0] = 0.071857
                    spine_bend_eb.head[1] = -0.508136
                    spine_bend_eb.head[2] = z_min + (z_max-z_min) * (spine_idx-1)/(total_spine_found-1)

                    spine_bend_eb.tail = spine_bend_eb.head + Vector((0.0, 0.0, 0.003))
                    spine_bend_eb.roll = 0.0
                    
                    # Set properties and shapes
                    bpy.ops.object.mode_set(mode='POSE')
                    
                    spine_bend_pb = get_pose_bone(spine_bend_name)
                    cs_name = 'cs_solid_plane_2'
                    spine_bend_pb['normal_shape'] = cs_name
                    spine_bend_pb['proxy'] = 'c_spine_' + idx + '_bend.x'
                    spine_bend_pb['select_shape'] = 'cs_solid_plane_2_sel'
        
                    # Custom shape                
                    spine_bend_pb.custom_shape = get_object(cs_name)
                    
                    # Set layers     
                    set_bone_layer(spine_bend_pb.bone, 1)

                    # Set group colors
                    set_bone_color_group(rig, spine_bend_pb, 'body.x')
                    
                    bpy.ops.object.mode_set(mode='EDIT')                
    
    for b in bones_to_create:
        bpy.ops.object.mode_set(mode='EDIT')
        
        picker_parent_bone = get_edit_bone("Picker")

        if picker_parent_bone:
            ebone = rig.data.edit_bones.new(b)
            ebone.parent = picker_parent_bone

            # Set transforms
            ebone.head, ebone.tail, ebone.roll = dict_bones[b][0], dict_bones[b][1], dict_bones[b][2]
            ebone.use_deform = False

            # Set properties and shapes
            bpy.ops.object.mode_set(mode='POSE')
            
            pbone = get_pose_bone(b)

            if len(dict_bones[b][3]) > 0:
                for prop in dict_bones[b][3]:
                    pbone[prop[0]] = prop[1]
            
            # Old old file retro-compatibility -Check the custom shape is in the scene, otherwise append it from the template file
            if len(pbone.keys()):
                if "normal_shape" in pbone.keys():
                    if get_object(pbone["normal_shape"]) == None:
                        obj_to_append = [pbone["normal_shape"]]  # , pbone["normal_shape"] + "_sel"]
                        append_from_arp(nodes=obj_to_append, type="object")
                        print("Appended custom shape:", obj_to_append)

            # Custom shape         
            pbone.custom_shape = get_object(dict_bones[b][5])

            # Set layers          
            if len(pbone.keys()):               
                if "proxy" in pbone.keys():                 
                    if get_pose_bone(pbone["proxy"]):
                        proxy_bone = get_pose_bone(pbone["proxy"])                     
                        for i, l in enumerate(pbone.bone.layers):
                            pbone.bone.layers[i] = proxy_bone.bone.layers[i]
                      
                    else:# no target bone found, set in layer 1 by default                     
                        set_bone_layer(pbone.bone, 1)

            # Set group colors
            try:
                pbone.bone_group = rig.pose.bone_groups[dict_bones[b][4]]
            except:
                print('Bone group "body ' + dict_bones[b][4] + ' not found')    
    
    
    if total_spine_found <= 3:
        bpy.ops.object.mode_set(mode='EDIT')
        
        third_spine = get_edit_bone(third_spine_name)
        if third_spine:
            delete_edit_bone(third_spine)

        second_spine = get_edit_bone(second_spine_name)
        second_spine_bend = get_edit_bone("c_spine_02_bend_proxy.x")
        if total_spine_found <= 2:
            if second_spine:
                delete_edit_bone(second_spine)
            if second_spine_bend:
                delete_edit_bone(second_spine_bend)

        first_spine = get_edit_bone(first_spine_name)
        first_spine_bend = get_edit_bone(first_spine_bend_name)
        waist_bend = get_edit_bone(waist_bend_name)

        if total_spine_found <= 1:
            if first_spine:
                delete_edit_bone(first_spine)
            if first_spine_bend:
                delete_edit_bone(first_spine_bend)
            if waist_bend:
                delete_edit_bone(waist_bend)
    
    
    
    
def _remove_picker():
    scn = bpy.context.scene

    # Save current mode
    current_mode = bpy.context.mode
    bpy.ops.object.mode_set(mode='POSE')

    # Delete rig_ui
    for child in bpy.context.active_object.children:
        if "cam_ui" in child.name:
            bpy.data.objects.remove(child)
            break

    for child in bpy.context.active_object.children:
        if "rig_ui" in child.name and child.type == "EMPTY":
            delete_children(child, "OBJECT")
            break

    # Delete proxy bones
    bpy.ops.object.mode_set(mode='EDIT')
    delete_children(get_edit_bone("Picker"), "EDIT_BONE")

    # restore saved mode
    restore_current_mode(current_mode)


def _mirror_picker():
    pbones = get_selected_pose_bones()
    sides = ['.l', '.r']
    for pbone in pbones:
        if pbone.name[-2:] in sides:
            if pbone.name[-2:] == sides[0]:
                opposite = sides[1]
            else:
                opposite = sides[0]

            opposite_bone = bpy.context.active_object.pose.bones[pbone.name[:-2] + opposite]
            opposite_bone.location = pbone.location
            opposite_bone.location[0] *= -1
            opposite_bone.rotation_euler = pbone.rotation_euler
            opposite_bone.rotation_euler[1] *= -1
            opposite_bone.rotation_euler[2] *= -1
            opposite_bone.rotation_quaternion = pbone.rotation_quaternion
            opposite_bone.scale = pbone.scale


def _move_picker_layout(state, self):
    bpy.ops.object.mode_set(mode='POSE')
    bpy.ops.pose.select_all(action='DESELECT')

    if state == 'start':
        _value = False
    if state == "end":
        _value = True

    # disable picker
    try:
        bpy.context.scene.Proxy_Picker.active = _value
    except:
        pass

    if state == "end":
        # Bake the pose transforms to edit transforms
        # save the proxy pose bones transf in a dict
        pbone_dict = {}

        for pbone in bpy.context.active_object.pose.bones:
            if is_proxy_bone(pbone):
                pbone_dict[pbone.name] = pbone.head.copy(), pbone.tail.copy(), pbone.matrix.copy()

        bpy.ops.object.mode_set(mode='EDIT')

        # disable mirror
        mirror_state = bpy.context.object.data.use_mirror_x
        bpy.context.object.data.use_mirror_x = False

        # apply to edit bones
        for pose_bone in pbone_dict:
            ebone = bpy.context.active_object.data.edit_bones[pose_bone]
            ebone.matrix = pbone_dict[pose_bone][2]
            ebone.head = pbone_dict[pose_bone][0]
            ebone.tail = pbone_dict[pose_bone][1]

        # enable mirror state
        bpy.context.object.data.use_mirror_x = mirror_state

        # reset pose transf
        bpy.ops.object.mode_set(mode='POSE')

        for pbone in bpy.context.active_object.pose.bones:
            if is_proxy_bone(pbone):
                pbone.scale = [1.0, 1.0, 1.0]
                pbone.location = [0.0, 0.0, 0.0]
                pbone.rotation_euler = [0.0, 0.0, 0.0]

    # lock/unlock bone transforms
    for pbone in bpy.context.active_object.pose.bones:
        if not is_proxy_bone(pbone):
            continue

        # some bones may have constraints
        if len(pbone.constraints) > 0:
            for cns in pbone.constraints:
                enable_constraint(cns, _value)
                
        if pbone.name.startswith("c_morph_") or pbone.name.startswith("c_iris") or pbone.name.startswith("c_pupil"):
            continue

        pbone.lock_location[0] = pbone.lock_location[1] = pbone.lock_location[2] = _value
        pbone.lock_scale[0] = pbone.lock_scale[1] = pbone.lock_scale[2] = _value
        pbone.rotation_mode = 'XYZ'
        pbone.lock_rotation[0] = pbone.lock_rotation[1] = pbone.lock_rotation[2] = _value

    # get all UI objects
    _mesh = None
    objects_list = []
    rig_ui = None

    # get rig_ui
    for child in bpy.context.active_object.children:
        if 'rig_ui' in child.name:
            rig_ui = child

            # get picker_background
    for obj in bpy.data.objects:
        if 'picker_background' in obj.name:
            objects_list.append(obj.name)
            break

    if rig_ui == None:
        self.report({'INFO'}, "Rig_ui object not found, parent it to the armature.")
        return

    for child in rig_ui.children:
        if 'label' in child.name:
            objects_list.append(child.name)
        if '_mesh' in child.name:
            objects_list.append(child.name)

    # lock/unlock objects selection and transform
    for obj in objects_list:
        if bpy.data.objects.get(obj):
            child = bpy.data.objects[obj]
            child.hide_select = _value
            child.lock_location[0] = _value
            child.lock_location[1] = _value
            child.lock_location[2] = _value
            child.lock_scale[0] = _value
            child.lock_scale[1] = _value
            child.lock_scale[2] = _value
            child.rotation_mode = 'XYZ'
            child.lock_rotation[0] = _value
            child.lock_rotation[1] = _value
            child.lock_rotation[2] = _value


def _screenshot_head_picker(filepath):
    current_obj = bpy.context.active_object
    scn = bpy.context.scene
    directory = bpy.path.abspath(filepath)

    # define the image name
    # file_name = 'picker_screenshot'

    # save render pref
    base_res_x = scn.render.resolution_x
    base_res_y = scn.render.resolution_y
    base_percentage = scn.render.resolution_percentage

    # set new render pref
    scn.render.resolution_x = int(512 * 1.3)
    scn.render.resolution_y = int(512)
    scn.render.resolution_percentage = 100

    # render
    show_overlays_state = bpy.context.space_data.overlay.show_overlays
    bpy.context.space_data.overlay.show_overlays = False
    bpy.ops.render.opengl(view_context=True)
    bpy.context.space_data.overlay.show_overlays = show_overlays_state

    if directory[-4:] != '.png':
        directory += '.png'
    bpy.data.images['Render Result'].save_render(directory)

    # backup the render pref
    scn.render.resolution_x = base_res_x
    scn.render.resolution_y = base_res_y
    scn.render.resolution_percentage = base_percentage

    # delete current empty image
    if bpy.data.objects.get('picker_background'):
        bpy.data.objects.remove(bpy.data.objects['picker_background'], do_unlink=True)

    # create empty image
    if bpy.data.objects.get('picker_background') == None:
        empty_image = bpy.data.objects.new("picker_background", None)      
        scn.collection.objects.link(empty_image)
        empty_image.empty_display_type = 'IMAGE'
        empty_image.rotation_euler[0] = radians(90)
        img = bpy.data.images.load(directory)
        empty_image.data = img
        empty_image.empty_image_offset[0] = -0.5
        empty_image.empty_image_offset[1] = -1.0

        # get UI mesh object
        rig_ui = None
        for child in current_obj.children:
            if 'rig_ui' in child.name:
                rig_ui = child
                break

        for child in rig_ui.children:
            if '_mesh' in child.name:
                ui_mesh = child
                break

        # find upper vert and deeper verts
        up_val = ui_mesh.data.vertices[0].co[2]
        vert_up = ui_mesh.data.vertices[0]
        deep_val = ui_mesh.data.vertices[0].co[1]
        vert_deep = ui_mesh.data.vertices[0]

        for vert in ui_mesh.data.vertices:
            if vert.co[2] > up_val:
                up_val = vert.co[2]
                vert_up = vert

            if vert.co[1] > deep_val:
                deep_val = vert.co[1]
                vert_deep = vert

        vert_up_global = ui_mesh.matrix_world @ vert_up.co
        vert_deep_global = ui_mesh.matrix_world @ vert_deep.co

        ui_width = ui_mesh.dimensions[2]
        fac = 0.46
        empty_image.scale = [ui_width * fac, ui_width * fac, ui_width * fac]

        empty_image.location[0] = 0.0
        empty_image.location[1] = vert_deep_global[1] - (0.001 * ui_width)
        empty_image.location[2] = vert_up_global[2]

        if len(scn.keys()):
            if 'Proxy_Picker' in scn.keys():
                if scn.Proxy_Picker.active:
                    empty_image.hide_select = True

        empty_image.location = rig_ui.matrix_world.inverted() @ empty_image.location
        empty_image.scale = rig_ui.matrix_world.inverted() @ empty_image.scale
        empty_image.parent = rig_ui

        current_mode = bpy.context.mode

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

        set_active_object(current_obj.name)
        bpy.ops.object.mode_set(mode=current_mode)


def _assign_colors():
    scene = bpy.context.scene

    # Controls bones color
    _bone_groups = bpy.context.active_object.pose.bone_groups

    # Right
    _bone_groups["body.r"].colors.normal = scene.color_set_right

    for i, channel in enumerate(_bone_groups["body.r"].colors.select):
        _bone_groups["body.r"].colors.select[i] = scene.color_set_right[i] + 0.4

    for i, channel in enumerate(_bone_groups["body.r"].colors.active):
        _bone_groups["body.r"].colors.active[i] = scene.color_set_right[i] + 0.5

    if _bone_groups.get("body.r_sel"):
        for i, channel in enumerate(_bone_groups["body.r_sel"].colors.normal):
            _bone_groups["body.r_sel"].colors.normal[i] = scene.color_set_right[i] + 0.6

            # Middle
    _bone_groups["body.x"].colors.normal = scene.color_set_middle

    for i, channel in enumerate(_bone_groups["body.x"].colors.select):
        _bone_groups["body.x"].colors.select[i] = scene.color_set_middle[i] + 0.4

    for i, channel in enumerate(_bone_groups["body.x"].colors.active):
        _bone_groups["body.x"].colors.active[i] = scene.color_set_middle[i] + 0.5

    if _bone_groups.get("body.x_sel"):
        for i, channel in enumerate(_bone_groups["body.x_sel"].colors.normal):
            _bone_groups["body.x_sel"].colors.normal[i] = scene.color_set_middle[i] + 0.6

            # Left
    _bone_groups["body.l"].colors.normal = scene.color_set_left

    for i, channel in enumerate(_bone_groups["body.l"].colors.select):
        _bone_groups["body.l"].colors.select[i] = scene.color_set_left[i] + 0.4

    for i, channel in enumerate(_bone_groups["body.l"].colors.active):
        _bone_groups["body.l"].colors.active[i] = scene.color_set_left[i] + 0.5

    if _bone_groups.get("body.l_sel"):
        for i, channel in enumerate(_bone_groups["body.l_sel"].colors.normal):
            _bone_groups["body.l_sel"].colors.normal[i] = scene.color_set_left[i] + 0.6

    # Materials
    _materials = bpy.context.blend_data.materials

    for mat in _materials:
        # Right
        if 'cs_' in mat.name and "_blue" in mat.name:
            # Selection color
            if '_sel' in mat.name:
                for i, channel in enumerate(mat.diffuse_color):
                    if i == 3:
                        break
                    mat.diffuse_color[i] = scene.color_set_right[i] + 0.4
            # Normal color
            else:
                for i, channel in enumerate(mat.diffuse_color):
                    if i == 3:
                        break
                    mat.diffuse_color[i] = scene.color_set_right[i]

        # Middle
        if 'cs_' in mat.name and "_green" in mat.name:
            # Selection color
            if '_sel' in mat.name:
                for i, channel in enumerate(mat.diffuse_color):
                    if i == 3:
                        break
                    mat.diffuse_color[i] = scene.color_set_middle[i] + 0.4
            # Normal color
            else:
                for i, channel in enumerate(mat.diffuse_color):
                    if i == 3:
                        break
                    mat.diffuse_color[i] = scene.color_set_middle[i]

        # Left
        if 'cs_' in mat.name and "_red" in mat.name:
            # Selection color
            if '_sel' in mat.name:
                for i, channel in enumerate(mat.diffuse_color):
                    if i == 3:
                        break
                    mat.diffuse_color[i] = scene.color_set_left[i] + 0.4
            # Normal color
            else:
                for i, channel in enumerate(mat.diffuse_color):
                    if i == 3:
                        break
                    mat.diffuse_color[i] = scene.color_set_left[i]

        # Panel back
        if 'cs' in mat.name and '_ui' in mat.name:
            mat.diffuse_color = (scene.color_set_panel[0], scene.color_set_panel[1], scene.color_set_panel[2], 1.0)
            mat.specular_color = (0, 0, 0)

        # Panel buttons
        if 'cs' in mat.name and 'button' in mat.name:
            for i, channel in enumerate(mat.diffuse_color):
                if i == 3:
                    break
                mat.diffuse_color[i] = scene.color_set_panel[i] + 0.2
            mat.specular_color = (0, 0, 0)

        # Panel text
        if ('cs' in mat.name and '_black' in mat.name) or ('cs' in mat.name and '_text' in mat.name) or (
                'test_blend' in mat.name):
            mat.diffuse_color = (scene.color_set_text[0], scene.color_set_text[1], scene.color_set_text[2], 1.0)
            mat.specular_color = (0, 0, 0)


def _clean_skin():
    head_remove = ['c_arm_twist', 'arm_stretch', 'c_elbow_bend', 'forearm_stretch', 'c_arm_bend', 'forearm_twist']
    head_bones = ['head.x', 'c_skull_01.x', 'c_skull_02.x', 'c_skull_03.x', 'c_jawbone.x', 'c_eyelid_corner_02']
    sides = ['.l', '.r']
    obj = bpy.context.active_object

    for key in head_remove:

        for side in sides:
            vgroup_exist = False
            try:
                obj.vertex_groups[key + side]
                vgroup_exist = True
            except:
                print("Vertex Group not found for transfer: ", key)

            if vgroup_exist:
                for vgroup in head_bones:
                    head_exist = False
                    try:
                        obj.vertex_groups[vgroup]
                        head_exist = True
                    except:
                        pass

                    if head_exist:
                        obj.modifiers.new("VWMix", 'VERTEX_WEIGHT_MIX')
                        """
                        for i in range(0,15):
                            bpy.ops.object.modifier_move_up(modifier="VWMix")
                        """

                        bpy.context.active_object.modifiers["VWMix"].vertex_group_a = key + side
                        bpy.context.active_object.modifiers["VWMix"].vertex_group_b = vgroup
                        bpy.context.active_object.modifiers["VWMix"].mix_mode = 'SUB'
                        bpy.context.active_object.modifiers["VWMix"].mix_set = 'ALL'
                        bpy.context.active_object.modifiers["VWMix"].mask_constant = 1000

                        apply_modifier("VWMix")


def delete_data(data_to_delete):
    for data_node in data_to_delete:
        data_type, data = data_to_delete[data_node]
        if data_type == "MESH":
            try:
                bpy.data.meshes.remove(data)
            except:
                pass
        if data_type == "ARMATURE":
            try:
                bpy.data.armatures.remove(data)
            except:
                pass


def _delete_arp():
    rig = None
    active_obj = bpy.context.active_object
    
    if active_obj:
        if active_obj.type == "ARMATURE":
            rig = get_object(active_obj.name)

    # if the rig is selected, delete related collections and objects related to it
    if rig:
        # make sure to unparent meshes from the armature
        for child in rig.children:
            if child.type == "MESH":
                obj_mat = child.matrix_world.copy()
                child.parent = None
                child.matrix_world = obj_mat

        # WARNING, DIRTY DEBUG 2.8
        # the rig_add armature cannot be deleted for some reasons in Blender 2.8 (missing referenced)
        # delete it first to fix it
        master = rig
        if rig.parent:
            master = rig.parent
            if master.parent:
                master = master.parent

        for child in master.children:
            if "rig_add" in child.name and child.type == "ARMATURE":
                bpy.data.objects.remove(child, do_unlink=True, do_id_user=True, do_ui_user=True)
                break

        # find children collections
        link_collections = []
        for col in master.users_collection:
            link_collections.append(col.name)
            for data_col in bpy.data.collections:
                for child in data_col.children:
                    if child == col and not data_col in link_collections:
                        link_collections.append(data_col.name)

        if 'col' in locals():
            del col

        # find the cs collec
        col_rig = get_rig_collection(rig)
        col_master = get_master_collection(col_rig)
        cs_collec = get_cs_collection(col_master)        
      
        if cs_collec:
            link_collections.append(cs_collec.name)

        data_to_delete = {}

        # delete rig hierarchy
        data_to_delete[rig.name] = rig.type, rig.data
        delete_children(master, "OBJECT")

        # delete cs objects
        if cs_collec:
            for cs_object in cs_collec.objects:
                # get the object data for removal later
                if cs_object.data:
                    data_to_delete[cs_object.name] = cs_object.type, cs_object.data

                # delete the object
                bpy.data.objects.remove(cs_object, do_unlink=True, do_id_user=True, do_ui_user=True)

        if 'col' in locals():
            del col
        if 'obj' in locals():
            del obj

        # update
        bpy.data.collections.update()

        # delete collections
        for col_name in link_collections:
            col = bpy.data.collections.get(col_name)
            if col:
                # safety check, if some objects are left in the collection, make sure to assign them
                # to the scene collection before, if they're not in any other collec
                if len(col.objects) != 0:
                    for ob in col.objects:
                        if len(ob.users_collection) < 2:
                            try:
                                bpy.context.scene.collection.objects.link(ob)
                            except:
                                pass
                # delete the collec
                bpy.data.collections.remove(col)

        # update
        bpy.data.collections.update()
        # trigger the scene update by adding an empty and removing it, otherwise crashes after collection deletion
        bpy.ops.object.select_all(action='DESELECT')
        bpy.ops.object.empty_add(type='PLAIN_AXES', location=(0, 0, 0))
        bpy.ops.object.delete(use_global=False)

        delete_data(data_to_delete)

    else:# if the rig is not selected, find the cs collection and find dependencies
        cs_collec = get_cs_collection(None)
        master_collec = None

        if cs_collec:
            print("cs_collec", cs_collec.name)
            for collec in bpy.data.collections:
                for col_child in collec.children:
                    if cs_collec == col_child:
                        master_collec = collec
                        break

        if master_collec:
            print("master_collec", master_collec.name)
            # find children collections
            link_collections = []
            for col in master_collec.children:
                link_collections.append(col.name)
                for _col in bpy.data.collections:
                    for child in _col.children:
                        if child == col and not _col in link_collections:
                            link_collections.append(_col.name)

            # delete cs objects
            data_to_delete = {}
            for colname in link_collections:
                col = bpy.data.collections.get(colname)
                for obj in col.objects:
                    if (obj.type == "MESH" and obj.name.startswith("cs_")) or obj.type != "MESH":

                        if obj.data:
                            data_to_delete[obj.name] = obj.type, obj.data
                        bpy.data.objects.remove(obj, do_unlink=True, do_ui_user=True)

            # delete collections
            for colname in link_collections:
                col = bpy.data.collections.get(colname)
                if col == None:
                    continue

                # safety check, if some objects are left in the collection, make sure to assign them
                # to the scene collection before, if they're not in any other collec
                if len(col.objects) != 0:
                    for ob in col.objects:
                        if len(ob.users_collection) < 2:
                            try:
                                bpy.context.scene.collection.objects.link(ob)
                            except:
                                pass

                bpy.data.collections.remove(col, do_unlink=True)

            # update
            bpy.data.collections.update()

            delete_data(data_to_delete)


def _append_arp(rig_type):
    context = bpy.context
    scene = context.scene   

    # get the current rigs to find the newly added later by comparing (clumsy)
    armatures = []
    for o in bpy.data.objects:
        if o.type == "ARMATURE":
            armatures.append(o.name)    
    
    try:
        bpy.ops.object.mode_set(mode='OBJECT')
    except:
        pass

    addon_directory = os.path.dirname(os.path.abspath(__file__))
    filepath = addon_directory + "/armature_presets/" + rig_type + ".blend"

    # Load the objects in the blend file datas
    with bpy.data.libraries.load(filepath, link=False) as (data_from, data_to):
        #data_to.objects = data_from.objects
        data_to.collections = data_from.collections

    # check - is it in local view mode?
    # local view removed from Blender 2.8? Disable it for now
    """
    if context.space_data.lock_camera_and_layers == False:
        context.space_data.lock_camera_and_layers = True
        context.evaluated_depsgraph_get().update()
    """

    for collec in data_to.collections:
        if tnzpv:
            # only append the master collection or TNZPV collection name format
            if len(collec.children) > 0 or len(collec.name.split('_')) == 4:
                bpy.context.scene.collection.children.link(collec)
        else:
            # for ARP rigs, only append the master collection
            if len(collec.children) > 0:
                bpy.context.scene.collection.children.link(collec)
            # otherwise it's a custom rig, append the collection, must be a single collection
            if len(data_to.collections) == 1:
                bpy.context.scene.collection.children.link(collec)

    bpy.context.space_data.overlay.show_relationship_lines = False
    
    bpy.ops.object.select_all(action='DESELECT')
    
    try:
        # find the newly added rig (clumsy)
        rig_name = ''
        
        for o in bpy.data.objects:
            if o.type == "ARMATURE":
                if not o.name in armatures:
                    rig_name = o.name
                    break
        
        set_active_object(rig_name)
        rig = get_object(rig_name)
        
        bpy.ops.object.mode_set(mode='POSE')
        
        if bpy.app.version >= (3,0,0):
            convert_drivers_cs_to_xyz(rig)
            
        limb_sides.get_multi_limbs()
        arm_sides = limb_sides.arm_sides
        leg_sides = limb_sides.leg_sides
        head_sides = limb_sides.head_sides
        
        print("Set default settings...")
        
        # set default settings
        prefs = bpy.context.preferences.addons[__package__].preferences
        
        for side in arm_sides:
            c_hand_ik_name = ard.arm_bones_dict["hand"]["control_ik"]
            c_hand_ik = get_pose_bone(c_hand_ik_name+side)        
            default_IKFK = prefs.default_ikfk_arm
            def_val = 0.0 if default_IKFK == 'IK' else 1.0
            set_prop_setting(c_hand_ik, 'ik_fk_switch', 'default', def_val)         
            c_hand_ik['ik_fk_switch'] = def_val

        for side in leg_sides:
            c_foot_ik_name = ard.leg_bones_dict['foot']['control_ik']
            c_foot_ik = get_pose_bone(c_foot_ik_name+side)        
            default_IKFK = prefs.default_ikfk_leg
            def_val = 0.0 if default_IKFK == 'IK' else 1.0
            set_prop_setting(c_foot_ik, 'ik_fk_switch', 'default', def_val)
            c_foot_ik['ik_fk_switch'] = def_val
            
        for side in head_sides:
            c_head_name = ard.heads_dict["control"]
            c_head = get_pose_bone(c_head_name[:-2]+side)
            default_head_lock = prefs.default_head_lock
            def_val = 1 if default_head_lock else 0
            set_prop_setting(c_head, 'head_free', 'default', def_val)          
            c_head['head_free'] = def_val
            
        
        bpy.ops.transform.translate(value=(0, 0, 0))# update hack
        
    except:
        pass


def _set_transform_constraints():
    # set transform constraints factor according to current units
    print("Set transform constraints values...")
    scn = bpy.context.scene
    
    bpy.ops.object.mode_set(mode='POSE')
    
    units_length = scn.unit_settings.scale_length

    # get the constraints transform values from file
    addon_directory = os.path.dirname(os.path.abspath(__file__))
    filepath = addon_directory + "/auto_rig_datas_export.py"
    file = open(filepath, 'rU')
    file_lines = file.readlines()
    dict_string = str(file_lines[0])
    file.close()

    # set values
    dict_bones = ast.literal_eval(dict_string)

    sides = limb_sides.leg_sides
    for side in sides:
        for pbone_name in dict_bones:
            pbone = get_pose_bone(pbone_name+side)
            if pbone:
                cns = pbone.constraints.get(dict_bones[pbone_name][0])
                if cns:
                    if scn.arp_retro_constraints:  # keep older, erroneous constraints for backward-compatibility with animations (user choice)
                        cns.from_min_x = dict_bones[pbone_name][1][0] * 1 / units_length
                        cns.from_max_x = dict_bones[pbone_name][1][1] * 1 / units_length
                        cns.from_min_y = dict_bones[pbone_name][1][2] * 1 / units_length
                        cns.from_max_y = dict_bones[pbone_name][1][3] * 1 / units_length
                        cns.from_min_z = dict_bones[pbone_name][1][4] * 1 / units_length
                        cns.from_max_z = dict_bones[pbone_name][1][5] * 1 / units_length
                    else:
                        base_length = dict_bones[pbone_name][2]
                        #print("SETTING TRANSFORM CNS OF", pbone.name)
                        cns.from_min_x = dict_bones[pbone_name][1][0] * (1 / units_length) * ((pbone.length * units_length) / base_length)
                        cns.from_max_x = dict_bones[pbone_name][1][1] * (1 / units_length) * ((pbone.length * units_length) / base_length)
                        cns.from_min_y = dict_bones[pbone_name][1][2] * (1 / units_length) * ((pbone.length * units_length) / base_length)
                        cns.from_max_y = dict_bones[pbone_name][1][3] * (1 / units_length) * ((pbone.length * units_length) / base_length)
                        cns.from_min_z = dict_bones[pbone_name][1][4] * (1 / units_length) * ((pbone.length * units_length) / base_length)
                        cns.from_max_z = dict_bones[pbone_name][1][5] * (1 / units_length) * ((pbone.length * units_length) / base_length)

    # set c_foot_roll_cursor speed/factor constraints
    foot_roll_bones = ["c_foot_bank_01", "c_foot_heel", "c_foot_bank_02", "c_toes_end"]

    for side in sides:
    
        bpy.ops.object.mode_set(mode='EDIT')
        
        foot_ref = get_edit_bone("foot_ref" + side)
        roll_fac = 1.0
        if "roll_cursor_fac" in foot_ref.keys():
            roll_fac = foot_ref["roll_cursor_fac"]
            
        bpy.ops.object.mode_set(mode='POSE')
        
        for bname in foot_roll_bones:
            roll_pbone = get_pose_bone(bname + side)
            for cns in roll_pbone.constraints:
                if cns.type != "TRANSFORM":
                    continue
                cns.from_min_x *= 1 / roll_fac
                cns.from_max_x *= 1 / roll_fac
                cns.from_min_y *= 1 / roll_fac
                cns.from_max_y *= 1 / roll_fac
                cns.from_min_z *= 1 / roll_fac
                cns.from_max_z *= 1 / roll_fac


def _reset_stretches():    
    bpy.context.scene.tool_settings.use_keyframe_insert_auto = False
  
    bpy.ops.object.mode_set(mode='POSE')
    
    bpy.ops.pose.select_all(action='SELECT')
    
    # store active pose
    bpy.ops.pose.copy()
    
    # need to reset the pose
    auto_rig_reset.reset_all()
    
    # reset stretches
    for pbone in bpy.context.active_object.pose.bones:
        if len(pbone.constraints):
            for cns in pbone.constraints:
                if cns.type != "STRETCH_TO":
                    continue
                    
                cns.rest_length = 0.0
                
                # Backward-compatibility, Blender 2.81 and before does not support Swing setting for stretch to constraints
                # fallback to 'PLANE_X' instead
                if bpy.app.version < (2,82,0) and cns.keep_axis == '':                 
                    cns.keep_axis = 'PLANE_X'

    # restore the pose
    bpy.ops.pose.paste(flipped=False)


def _remove_muscles(self):
    arm_muscles = ["biceps", "biceps_root_01", "biceps_root_02", "biceps_tip"]

    for side in Limb_Sides.arm_sides:
        for bone in arm_muscles:
            bone_muscle = get_edit_bone(bone + side)
            if bone_muscle:
                bpy.context.active_object.data.edit_bones.remove(bone_muscle)

    return True


def _add_muscles(self):
    scn = bpy.context.scene

    arm_biceps_name = "biceps"
    arm_biceps_tip_name = "biceps_tip"

    for side in Limb_Sides.arm_sides:

        print("Arm muscles [", side, "]")

        bpy.ops.object.mode_set(mode='EDIT')

        # Create the muscle attachments bones - Tip
        biceps_tip_bone = get_edit_bone(arm_biceps_tip_name + side)

        if biceps_tip_bone == None:
            biceps_tip_bone = bpy.context.active_object.data.edit_bones.new(arm_biceps_tip_name + side)

        biceps_tip_bone.parent = get_edit_bone("forearm_stretch" + side)

        set_bone_layer([9, 31], biceps_tip_bone)

        # position it
        forearm_bone = get_edit_bone("forearm" + side)

        fac = 1
        if side[-2:] == ".r":
            fac = -1

        biceps_tip_bone.head = forearm_bone.head + ((forearm_bone.tail - forearm_bone.head) * 0.2) - (
                forearm_bone.x_axis * fac * (forearm_bone.tail - forearm_bone.head).magnitude * 0.1)
        biceps_tip_bone.tail = biceps_tip_bone.head + Vector(
            (0, 0, (forearm_bone.tail - forearm_bone.head).magnitude * 0.1))
        biceps_tip_bone.use_deform = False

        # Create the main muscle bone
        biceps_bone = get_edit_bone(arm_biceps_name + side)

        if biceps_bone == None:
            biceps_bone = bpy.context.active_object.data.edit_bones.new(arm_biceps_name + side)

        biceps_bone.parent = get_edit_bone("arm_stretch" + side)

        set_bone_layer([9, 31], biceps_bone)

        # position it
        arm_bone = get_edit_bone("arm" + side)

        if arm_bone == None:
            self.report({"ERROR"}, "No arm bone found, could not create the muscles")
            return False

        fac = 1
        if side[-2:] == ".r":
            fac = -1

        biceps_bone.head = arm_bone.head + ((arm_bone.tail - arm_bone.head) * 0.2) - (
                arm_bone.x_axis * fac * (arm_bone.tail - arm_bone.head).magnitude * 0.2)
        biceps_bone.tail = get_edit_bone(arm_biceps_tip_name + side).head

        # set the roll
        bpy.ops.object.mode_set(mode='POSE')
        bpy.ops.pose.select_all(action='DESELECT')
        bpy.context.active_object.data.bones.active = bpy.context.active_object.pose.bones[arm_biceps_name + side].bone
        bpy.context.active_object.data.bones.active = bpy.context.active_object.pose.bones["arm" + side].bone
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.armature.calculate_roll(type='ACTIVE')

        # Create the muscle attachments bones - Root bones
        # bone 1 (stretch parent)
        biceps_root_bone1 = get_edit_bone("biceps_root_01" + side)
        biceps_bone = get_edit_bone(arm_biceps_name + side)

        if biceps_root_bone1 == None:
            biceps_root_bone1 = bpy.context.active_object.data.edit_bones.new("biceps_root_01" + side)

        biceps_root_bone1.parent = get_edit_bone("arm_stretch" + side)
        biceps_root_bone1.use_deform = False
        set_bone_layer([9, 31], biceps_root_bone1)

        # position it

        biceps_root_bone1.head = biceps_bone.head
        biceps_root_bone1.tail = biceps_bone.head + Vector(
            (0, 0, (biceps_bone.head - biceps_bone.tail).magnitude * 0.1))

        # bone 2 (twist parent)
        biceps_root_bone2 = get_edit_bone("biceps_root_02" + side)

        if biceps_root_bone2 == None:
            biceps_root_bone2 = bpy.context.active_object.data.edit_bones.new("biceps_root_02" + side)

        biceps_root_bone2.parent = get_edit_bone("c_arm_twist_offset" + side)
        biceps_root_bone2.use_deform = False
        set_bone_layer([9, 31], biceps_root_bone2)

        # position it
        biceps_root_bone2.head = biceps_bone.head
        biceps_root_bone2.tail = biceps_bone.head + Vector(
            (0, 0, (biceps_bone.head - biceps_bone.tail).magnitude * 0.1))

        # add the constraints
        bpy.ops.object.mode_set(mode='POSE')

        biceps = get_pose_bone(arm_biceps_name + side)
        if len(biceps.constraints) > 0:
            for cns in biceps.constraints:
                biceps.constraints.remove(cns)

        copy_loc1 = biceps.constraints.new("COPY_LOCATION")
        copy_loc1.target = bpy.context.active_object
        copy_loc1.subtarget = "biceps_root_01" + side

        copy_loc2 = biceps.constraints.new("COPY_LOCATION")
        copy_loc2.target = bpy.context.active_object
        copy_loc2.subtarget = "biceps_root_02" + side
        copy_loc2.influence = 0.5

        stretch_to = biceps.constraints.new("STRETCH_TO")
        stretch_to.target = bpy.context.active_object
        stretch_to.subtarget = arm_biceps_tip_name + side
        stretch_to.influence = 1.0
        stretch_to.head_tail = 0.0


def _add_blink_pose(self):
    lvl = self.lvl
    action_name = self.blink_action
    
    
    print("Add blink pose...")
    # get side
    sel_bone_name = get_selected_pose_bones()[0].name
    side = get_bone_side(sel_bone_name)
    
    # get eyelids controllers
    eyelids = []
    for cname in ard.get_variable_eyelids(side, type='CONTROLLER', eye_sides=[side[-2:]], levels=[lvl+'_']):
        eyelids.append(cname)
        
    # corners
    eyelids.append('c_eyelid_corner_01'+side)
    eyelids.append('c_eyelid_corner_02'+side)    
    #   tweak bones
    eyel_twk_name = ard.eye_bones_dict['eyelid_twk_'+lvl]['name']+side    
    if get_data_bone(eyel_twk_name):
        eyelids.append(eyel_twk_name)
            
    c_eyel_main = get_pose_bone("c_eyelid_"+lvl+side)    
    act = bpy.data.actions.get(action_name)
    
    frame_tgt = 10# default position, fully closed at frame 10
    
    if self.cns_action and self.in_between_or_def == 'IN_BETWEEN':
        # get the current frame to insert keyframes, relative to the eyelid controller position
        current_loc = c_eyel_main.location[2]
        frame_tgt = (current_loc/self.cns_action.max)*10
    
    # setup action
    '''
    #   clear fcurves
    for fc in act.fcurves:
        if not fc.data_path.startswith('pose.bones'):
            continue
        # only remove same side
        bname = fc.data_path.split('"')[1]
        bside = get_bone_side(bname)
        if bside == side:
            act.fcurves.remove(fc)          
    '''
    for cname in eyelids:
        pb = get_pose_bone(cname)
       
        # trick to apply the current constraint transforms if any
        #print("  Apply matrix")
        pb_mat = pb.matrix.copy()
        pb.matrix = pb_mat
        bpy.context.evaluated_depsgraph_get().update()        
        
        # add keyframe
        for i in range(0,3):
            # loc
            fc_loc = act.fcurves.find('pose.bones["'+cname+'"].location', index=i)
            if fc_loc == None:
                fc_loc = act.fcurves.new('pose.bones["'+cname+'"].location', index=i)
            key = fc_loc.keyframe_points.insert(frame_tgt, pb.location[i])
            key.interpolation = "LINEAR"
            key = fc_loc.keyframe_points.insert(0, 0)
            key.interpolation = "LINEAR"
            # rot
            fc_rot = act.fcurves.find('pose.bones["'+cname+'"].rotation_euler', index=i)
            if fc_rot == None:
                fc_rot = act.fcurves.new('pose.bones["'+cname+'"].rotation_euler', index=i)
            key = fc_rot.keyframe_points.insert(frame_tgt, pb.rotation_euler[i])
            key.interpolation = "LINEAR"
            key = fc_rot.keyframe_points.insert(0, 0)
            key.interpolation = "LINEAR"
            # scale
            fc_s = act.fcurves.find('pose.bones["'+cname+'"].scale', index=i)
            if fc_s == None:
                fc_s = act.fcurves.new('pose.bones["'+cname+'"].scale', index=i)
            key = fc_s.keyframe_points.insert(frame_tgt, pb.scale[i])  
            key.interpolation = "LINEAR"
            key = fc_s.keyframe_points.insert(0, 1)
            key.interpolation = "LINEAR"
            
        # now keyframes are stored, zero out current pose
        reset_pbone_transforms(pb)        
        
        # setup action constraint
        cns = None
        if len(pb.constraints):
            for c in pb.constraints:
                if c.name == 'ActionBlink_'+lvl:            
                    cns = c
                    break
        
        if cns == None:
            cns = pb.constraints.new("ACTION")
            cns.name = 'ActionBlink_'+lvl
            
        cns.target = bpy.context.active_object
        cns.subtarget = "c_eyelid_"+lvl+side
        cns.action = act        
        cns.transform_channel = "LOCATION_Z"
        cns.target_space = "LOCAL"         
        cns.min = 0.0
        if self.cns_action == None or (self.cns_action and self.in_between_or_def == 'AS_CLOSED'):# set eyelid fully closed position only when setting up the action first time
            cns.max = c_eyel_main.location[2]   

        cns.frame_start = 0
        cns.frame_end = 10    
            
            
def _remove_blink_pose(self):
    # get side
    sel_bone_name = get_selected_pose_bones()[0].name
    side = get_bone_side(sel_bone_name)
    act = None
    lvl = self.lvl
    
    # get eyelids controllers
    eyelids = []
    for cname in ard.get_variable_eyelids(side, type='CONTROLLER', eye_sides=[side[-2:]], levels=[lvl+'_']):
        eyelids.append(cname)
    # corners
    eyelids.append('c_eyelid_corner_01'+side)
    eyelids.append('c_eyelid_corner_02'+side)
    #   tweak bones
    eyel_twk_name = ard.eye_bones_dict['eyelid_twk_'+lvl]['name']+side    
    if get_data_bone(eyel_twk_name):
        eyelids.append(eyel_twk_name)
    
    # remove constraints
    for cname in eyelids:      
        pb = get_pose_bone(cname)
        if len(pb.constraints):
            cns = pb.constraints.get('ActionBlink_'+lvl)
            if cns == None:
                print('Action Constraint not found')
                continue
            if act == None:
                act = cns.action
            if cns:
                pb.constraints.remove(cns)
                
    # remove keyframes
    if act:
        for fc in act.fcurves:
            if not fc.data_path.startswith('pose.bones'):
                continue
            # only remove same side
            # actions are per level already, no need to filter level
            bname = fc.data_path.split('"')[1]
            bside = get_bone_side(bname)            
            if bside == side:
                act.fcurves.remove(fc)
        
        
def _add_fist_ctrl(action_name, fist_type):
    # get side
    sel_bone_name = get_selected_pose_bones()[0].name
    side = get_bone_side(sel_bone_name)
    
    bpy.ops.object.mode_set(mode='EDIT')

    hand_bone = get_edit_bone("hand" + side)

    # is the bone already created?
    if get_edit_bone("c_fist" + side):
        print("c_fist" + side + " already created.")

    # else create bone
    else:
        new_bone = bpy.context.active_object.data.edit_bones.new("c_fist" + side)
        print("Created", "c_fist" + side)
        new_bone.head = hand_bone.head + (hand_bone.tail - hand_bone.head) * 1.0 + (hand_bone.tail - hand_bone.head).magnitude * hand_bone.z_axis
        new_bone.tail = new_bone.head + (hand_bone.tail - hand_bone.head)
        new_bone.roll = hand_bone.roll

    new_bone = get_edit_bone("c_fist" + side)

    # Set parent
    new_bone.parent = get_edit_bone(hand_bone.name)

    # Set layer
    bpy.ops.object.mode_set(mode='POSE')

    new_pbone = get_pose_bone("c_fist" + side)
    set_bone_layer(new_pbone.bone, 0)
   
    # Set rotation mode
    new_pbone.rotation_mode = 'XYZ'

    # Set transforms locks
    for i in range(0, 3):
        new_pbone.lock_location[i] = True
        new_pbone.lock_rotation[i] = True

    # Set limit constraint
    limit_cns = new_pbone.constraints.get("Limit Scale")
    if limit_cns == None:
        limit_cns = new_pbone.constraints.new("LIMIT_SCALE")
        limit_cns.name = "Limit Scale"
        limit_cns.use_min_x = limit_cns.use_min_y = limit_cns.use_min_z = limit_cns.use_max_x = limit_cns.use_max_y = limit_cns.use_max_z = True
        limit_cns.min_x = limit_cns.min_y = limit_cns.min_z = 0.5
        limit_cns.max_x = limit_cns.max_y = limit_cns.max_z = 1.5
        limit_cns.use_transform_limit = True
        limit_cns.owner_space = "LOCAL"

    # Set custom shape
    if get_object("cs_fist") == None:
        obj_to_append = ["cs_fist"]
        append_from_arp(nodes=obj_to_append, type='object')

        # parent it to the "cs_grp" object
        for obj in obj_to_append:
            if bpy.data.objects.get("cs_grp"):
                bpy.data.objects[obj].parent = bpy.data.objects["cs_grp"]

                # link to collection
                for collec in bpy.data.objects["cs_grp"].users_collection:
                    collec.objects.link(bpy.data.objects[obj])

            else:
                print("Could not find the cs_grp object to parent to")
        print("Appended cs_fist shapes")

    if get_object("cs_fist"):
        new_pbone.custom_shape = get_object("cs_fist")
    else:
        new_pbone.custom_shape = get_object("cs_torus_04_rot2")

    new_pbone.bone.show_wire = True

    # Set color group
    try:
        new_pbone.bone_group = bpy.context.active_object.pose.bone_groups["body" + side[-2:]]
    except:
        print('Bone group "body' + side[-2:] + ' not found')

        
    # Get fingers
    c_fingers_names = ard.fingers_control
    fingers_def = []    

    for fname in c_fingers_names:
        finger_name = fname + side
        ctrl_finger = get_pose_bone(finger_name)
        if ctrl_finger:
            fingers_def.append(ctrl_finger)

    # Setup Fingers Fist action & constraints
    # setup action
    act = bpy.data.actions.get(action_name)
    target_frame = 10 if fist_type == 'FIST' else -10
    
    #   clear fcurves
    for fc in act.fcurves:
        if not fc.data_path.startswith('pose.bones'):
            continue
        # only remove same side
        bname = fc.data_path.split('"')[1]
        bside = get_bone_side(bname)
        if bside == side:
            for kf in fc.keyframe_points:
                if kf.co[0] == target_frame:
                    fc.keyframe_points.remove(kf)
                    
    for pb in fingers_def:
        cname = pb.name
        cns_action = None
        
        # trick to apply the current constraint transforms if any
        print("  Apply matrix")
        pb_mat = pb.matrix.copy()
        pb.matrix = pb_mat
        
        # add keyframes
        print("  Add keyframes...") 
        
        for i in range(0,3):
            # loc
            dp = 'pose.bones["'+cname+'"].location'
            fc_loc = act.fcurves.find(dp, index=i)
            if fc_loc == None:
                fc_loc = act.fcurves.new(dp, index=i)
            key = fc_loc.keyframe_points.insert(target_frame, pb.location[i])
            key.interpolation = "LINEAR"
            key = fc_loc.keyframe_points.insert(0, 0)
            key.interpolation = "LINEAR"
            
            # rot
            dp = 'pose.bones["'+cname+'"].rotation_euler'
            fc_rot = act.fcurves.find(dp, index=i)
            if fc_rot == None:
                fc_rot = act.fcurves.new(dp, index=i)
            key = fc_rot.keyframe_points.insert(target_frame, pb.rotation_euler[i])
            key.interpolation = "LINEAR"
            key = fc_rot.keyframe_points.insert(0, 0)
            key.interpolation = "LINEAR"
            
            # scale
            dp = 'pose.bones["'+cname+'"].scale'
            fc_s = act.fcurves.find(dp, index=i)
            if fc_s == None:
                fc_s = act.fcurves.new(dp, index=i)
            key = fc_s.keyframe_points.insert(target_frame, pb.scale[i])  
            key.interpolation = "LINEAR"
            key = fc_s.keyframe_points.insert(0, 1)
            key.interpolation = "LINEAR"
            
        # now keyframes are stored, zero out current pose
        print("reset transf")
        reset_pbone_transforms(pb)
        
        print("  Setup constraints...")
        # setup action constraint
        if len(pb.constraints):
            for cns in pb.constraints:
                if cns.name == 'Action':                   
                    cns_action = cns
                    break

        if cns_action == None:      
            cns_action = pb.constraints.new("ACTION")
            cns_action.name = 'Action'
            
        cns_action.target = bpy.context.active_object
        cns_action.subtarget = "c_fist"+side
        cns_action.action = act        
        cns_action.transform_channel = "SCALE_Y"
        cns_action.target_space = "LOCAL"         
        cns_action.min = 1.5
        cns_action.max = 0.5    
        cns_action.frame_start = -10
        cns_action.frame_end = 10
        
    '''
    for pbone in fingers_def:
        # Constraint already created?
        create_cns = True

        if len(pbone.constraints):
            for cns in pbone.constraints:
                if cns.type == "ACTION":
                    print("Constraint already created")
                    create_cns = False

        # Create constraints
        if create_cns:
            print("Create constraint")
            action_cns = get_pose_bone(pbone.name).constraints.new("ACTION")

            action_cns.target = bpy.context.active_object
            action_cns.subtarget = "c_fist" + side
            action_cns.action = bpy.data.actions[action_name]
            action_cns.transform_channel = "SCALE_Y"
            action_cns.target_space = "LOCAL"
            action_cns.min = 1.5
            action_cns.max = 0.5
            action_cns.frame_start = -10
            action_cns.frame_end = 10
    '''


def _remove_fist_ctrl():
    # get side
    sel_bone_name = get_selected_pose_bones()[0].name
    side = get_bone_side(sel_bone_name)

    bpy.ops.object.mode_set(mode='EDIT')

    hand_bone = get_edit_bone("hand" + side)

    # is the bone already created?
    if get_edit_bone("c_fist" + side):
        delete_edit_bone(get_edit_bone("c_fist" + side))
        print("Removed", "c_fist" + side)

    bpy.ops.object.mode_set(mode='POSE')

    # Get fingers
    fingers = ["c_pinky", "c_ring", "c_middle", "c_index", "c_thumb"]
    fingers_def = []
    for finger in fingers:
        for i in range(0, 4):
            if get_pose_bone(finger + str(i) + side):
                ctrl_finger = get_pose_bone(finger + str(i) + side)
                if ctrl_finger.bone.layers[0]:  # if in layer 0, it's enabled
                    fingers_def.append(ctrl_finger)

        # base finger
        if get_pose_bone(finger + "1_base" + side):
            base_finger = get_pose_bone(finger + "1_base" + side)
            if base_finger.bone.layers[0]:  # if in layer 0, it's enabled
                fingers_def.append(base_finger)

    # Print debug
    print("\nFingers list:")
    for i in fingers_def:
        print(i.name)

    print("")

    for pbone in fingers_def:

        # Constraint already created?
        create_cns = True

        if len(pbone.constraints) > 0:
            for cns in pbone.constraints:
                if cns.type == "ACTION":
                    pbone.constraints.remove(cns)
                    print("Deleted constraint")


def _mirror_custom_shape():
    armature_name = bpy.context.active_object.name
    armature = bpy.data.objects[armature_name]
    cs_grp = None
    cs_collec = []
    real_cs_collec = True

    selected_pbone = get_selected_pose_bones()[0]

    armature_col = armature.users_collection[0]

    if selected_pbone.custom_shape:
        cshape = selected_pbone.custom_shape

        if cshape.parent:
            if "cs_grp" in cshape.parent.name:
                cs_grp = cshape.parent

        for i in cshape.users_collection:
            if i.name.endswith("_cs") or "_cs." in i.name:
                cs_collec.append(i)
        if len(cs_collec) == 0:
            print("cs collection not found, add to the first found collection or armature collection instead")
            if len(cshape.users_collection):
                cs_collec.append(cshape.users_collection[0])
            else:
                cs_collec.append(armature_col)
            real_cs_collec = False

    for bone in get_selected_pose_bones():
        bone_name = bone.name
        cs = bone.custom_shape
        side = bone.name[-2:]
        mirror_side = ""

        # lowercase
        if side == '.l':
            mirror_side = ".r"
        elif side == ".r":
            mirror_side = ".l"
        elif side == '_l':
            mirror_side = "_r"
        elif side == "_r":
            mirror_side = "_l"
        # uppercase
        if side == '.L':
            mirror_side = ".R"
        elif side == ".R":
            mirror_side = ".L"
        elif side == '_L':
            mirror_side = "_R"
        elif side == "_R":
            mirror_side = "_L"


            # if there's a mirrored bone
        mirror_bone = armature.pose.bones.get(bone.name[:-2] + mirror_side)
        if mirror_bone:
            pivot_mode = bpy.context.scene.tool_settings.transform_pivot_point
            if mirror_bone.custom_shape == None:
                mirror_bone.custom_shape = cs
            # if it's not already a custom shape, create it
            if not mirror_bone.custom_shape.name == 'cs_user_' + mirror_bone.name:
                # create the cs
                bpy.ops.object.mode_set(mode='OBJECT')
                bpy.ops.object.select_all(action='DESELECT')

                bpy.ops.mesh.primitive_plane_add(size=1, enter_editmode=False, location=(-0, 0, 0.0),
                                                 rotation=(0.0, 0.0, 0.0))
                mesh_obj = bpy.context.active_object
                mesh_obj.name = 'cs_user_' + mirror_bone.name
                mesh_obj.data = cs.data
                bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', object=True, obdata=True)
                mesh_obj.data.name = mesh_obj.name

                # mirror it
                bpy.ops.view3d.snap_selected_to_cursor(use_offset=False)
                bpy.context.scene.tool_settings.transform_pivot_point = 'CURSOR'
                bpy.ops.object.mode_set(mode='EDIT')
                bpy.ops.mesh.select_all(action='SELECT')

                bpy.ops.transform.mirror(constraint_axis=(True, False, False), orient_type='LOCAL')

                bpy.ops.object.mode_set(mode='OBJECT')

                # assign to bone
                mirror_bone.custom_shape = mesh_obj

                # assign to collection and parent
                if cs_grp:
                    mesh_obj.parent = cs_grp
                    for _col in mesh_obj.users_collection:
                        _col.objects.unlink(mesh_obj)
                    try:
                        bpy.context.scene.collection.objects.unlink(mesh_obj)
                    except:
                        pass

                    for col in cs_collec:
                        col.objects.link(mesh_obj)

                # hide shape
                try:
                    hide_object(mesh_obj)
                except:  # weird error 'StructRNA of type Object has been removed'
                    print("Error, could not hide shape")
                    pass


            else:  # if it's a custom shape, just set mesh data
                for col in cs_collec:
                    # only if it's really a "cs" collec
                    if real_cs_collec:
                        col.hide_viewport = False

                bpy.ops.object.mode_set(mode='OBJECT')
                bpy.ops.object.select_all(action='DESELECT')
                cust_shape = bpy.data.objects[mirror_bone.custom_shape.name]
                unhide_object(cust_shape)
                cust_shape.hide_select = False  # safety check

                # make sure the shape object is in collection
                if len(cust_shape.users_collection) == 0:
                    cs_collec[0].objects.link(cust_shape)

                # extra check in case the user has messed up collections...
                unhide_mirror_bone_collec = None
                if len(mirror_bone.custom_shape.users_collection):
                    if mirror_bone.custom_shape.users_collection[0].hide_viewport:
                        mirror_bone.custom_shape.users_collection[0].hide_viewport = False
                        unhide_mirror_bone_collec = mirror_bone.custom_shape.users_collection[0]

                set_active_object(mirror_bone.custom_shape.name)
                #print("SELECT", mirror_bone.custom_shape.name)
                mesh_obj = bpy.context.active_object
                mirror_bone.custom_shape.data = cs.data
                bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', object=False, obdata=True)
                try:
                    mesh_obj.data.name = mesh_obj.name
                except:
                    print("error with", bone)

                # mirror it
                bpy.ops.view3d.snap_selected_to_cursor(use_offset=False)
                bpy.context.scene.tool_settings.transform_pivot_point = 'CURSOR'
                bpy.ops.object.mode_set(mode='EDIT')
                bpy.ops.mesh.select_all(action='SELECT')
                bpy.ops.transform.mirror(constraint_axis=(True, False, False), orient_type='LOCAL')
                bpy.ops.object.mode_set(mode='OBJECT')

                for col in cs_collec:
                    if real_cs_collec:
                        # only if it's really a "cs" collec
                        col.hide_viewport = True

                hide_object(cust_shape)

                if unhide_mirror_bone_collec:
                    unhide_mirror_bone_collec.hide_viewport = True

            set_active_object(armature_name)
            bpy.ops.object.mode_set(mode='POSE')
            bpy.context.scene.tool_settings.transform_pivot_point = pivot_mode


def _edit_custom_shape():
    bone = bpy.context.active_pose_bone
    armature = bpy.context.active_object
    armature_name = armature.name
    cs = bpy.context.active_pose_bone.custom_shape
    cs_mesh = cs.data

    bpy.ops.object.posemode_toggle()

    # make sure the active collection is not hidden, otherwise we can't access the newly created object data
    active_collec = bpy.context.layer_collection
    if not active_collec.is_visible:
        for col in armature.users_collection:
            layer_col = search_layer_collection(bpy.context.view_layer.layer_collection, col.name)
            if layer_col.hide_viewport == False and col.hide_viewport == False:
                bpy.context.view_layer.active_layer_collection = layer_col
                break

    # create new mesh data
    bpy.ops.mesh.primitive_plane_add(size=1, enter_editmode=False, location=(-0, 0, 0.0), rotation=(0.0, 0.0, 0.0))

    mesh_obj = bpy.context.active_object
    mesh_obj.name = 'cs_user_' + bone.name

    if cs.name == "cs_user_" + bone.name:# make a mesh instance if it's a already edited
        mesh_obj.data = cs_mesh
        mesh_obj['delete'] = 1.0
    else:# else create new object data
        mesh_obj.data = cs_mesh.copy()
        mesh_obj.data.name = mesh_obj.name
        bone.custom_shape = mesh_obj

    # store the current armature name in a custom prop
    mesh_obj['arp_armature'] = armature_name

    if bone.custom_shape_transform:
        bone_transf = bone.custom_shape_transform
        mesh_obj.matrix_world = armature.matrix_world @ bone_transf.matrix
    else:
        mesh_obj.matrix_world = armature.matrix_world @ bone.matrix

    mesh_obj.scale *= get_custom_shape_scale(bone)
    mesh_obj.scale *= bone.length

    bpy.ops.object.mode_set(mode='EDIT')


def _apply_shape():
    bpy.ops.object.mode_set(mode='OBJECT')
    obj = get_object(bpy.context.active_object.name)
    obj_name = obj.name
    shape = get_object(obj_name)
    delete_obj = False
            
    arp_armature_name = None
    arp_armature = None

    if len(shape.keys()) > 0:
        for key in shape.keys():
            if 'delete' in shape.keys():
                delete_obj = True
            if 'arp_armature' in key:
                arp_armature_name = shape['arp_armature']
                arp_armature = get_object(arp_armature_name)

    if delete_obj:
        bpy.ops.object.delete(use_global=False)
    else:
        # assign to collection
        collec_fallback = None
        
        if arp_armature:
            if len(arp_armature.users_collection):
                for collec in arp_armature.users_collection:
                    collec_fallback = collec
                    
                    if len(collec.name.split('_')) == 1:# invalid collection name, must be xx_xx
                        continue
                    if collec.name.split('_')[1] == "rig" or collec.name.split('_')[1] == "grp":
                        cs_collec = bpy.data.collections.get(collec.name.split('_')[0] + '_cs')
                        
                        if cs_collec:
                            collec_fallback = cs_collec
                            # remove from root collection
                            if bpy.context.scene.collection.objects.get(shape.name):
                                bpy.context.scene.collection.objects.unlink(shape)
                            # remove from other collections
                            for other_collec in shape.users_collection:
                                other_collec.objects.unlink(shape)
                                
                            break
            else:
                print("Armature has no collection")
        else:
            print("Armature not set")
            
        # assign to cs collection     
        if collec_fallback:
            try:
                collec_fallback.objects.link(shape)
                print("assigned to collec", collec_fallback.name)
            except:
                pass
            
        # parent to cs_grp
        for o in bpy.data.objects:
            if o.name.startswith('cs_grp'):
                for c in o.users_collection:
                    if c == collec_fallback:
                        shape.parent = o
                        break
           
            if shape.parent:
                break
            

    # hide shape
    try:
        hide_object(shape)
    except:  # weird error 'StructRNA of type Object has been removed'
        print("Error, could not hide shape")
        pass

    if arp_armature:
        set_active_object(arp_armature.name)
        bpy.ops.object.mode_set(mode='POSE')


def _import_colors(filepath):
    scene = bpy.context.scene
    file = open(filepath, 'rU')
    file_lines = file.readlines()
    dict_string = str(file_lines[0])
    file.close()

    dict = ast.literal_eval(dict_string)

    scene.color_set_left = dict['left']
    scene.color_set_middle = dict['middle']
    scene.color_set_panel = dict['panel_back']
    scene.color_set_right = dict['right']
    scene.color_set_text = dict['panel_text']


def _export_colors(filepath):
    scene = bpy.context.scene

    # add extension
    if filepath[-3:] != ".py":
        filepath += ".py"

    file = open(filepath, "w", encoding="utf8", newline="\n")
    dict = {}

    dict['right'] = [scene.color_set_right[0], scene.color_set_right[1], scene.color_set_right[2]]
    dict['middle'] = [scene.color_set_middle[0], scene.color_set_middle[1], scene.color_set_middle[2]]
    dict['left'] = [scene.color_set_left[0], scene.color_set_left[1], scene.color_set_left[2]]
    dict['panel_back'] = [scene.color_set_panel[0], scene.color_set_panel[1], scene.color_set_panel[2]]
    dict['panel_text'] = [scene.color_set_text[0], scene.color_set_text[1], scene.color_set_text[2]]

    file.write(str(dict))

    # close file
    file.close()


def _export_rig_data(self):
    filepath = self.filepath
    export_ref_bones = self.ref_bones
    export_custom_shapes = self.custom_shapes

    cur_mode = get_current_mode()

    if filepath[-3:] != ".py":
        filepath += ".py"

    file = open(filepath, "w", encoding="utf8", newline="\n")


    if export_ref_bones:
        print("Export ref bone...")
        bpy.ops.object.mode_set(mode='EDIT')

        bones_dict = {}

        for bone in bpy.context.active_object.data.edit_bones:
            if bone.layers[17]:
                bones_dict[bone.name] = [bone.head[0], bone.head[1], bone.head[2]], [bone.tail[0], bone.tail[1], bone.tail[2]], bone.roll

        file.write(str(bones_dict))

    else:
        empty_dict = {}
        file.write(str(empty_dict))

    if export_custom_shapes:
        print("Export custom shapes...")
        bpy.ops.object.mode_set(mode='POSE')

        cs_dict = pose_bones_custom_shapes_to_dict(bpy.context.active_object.pose.bones)

        file.write("\n")
        file.write(str(cs_dict))

    # close file
    file.close()

    restore_current_mode(cur_mode)


def _import_rig_data(self):
    cur_mode = get_current_mode()

    filepath = self.filepath
    import_ref_bones = self.import_ref_bones   
    selection_only = self.selection_only   
    import_custom_shapes = self.import_custom_shapes  
    
    file = open(filepath, 'rU')
    file_lines = file.readlines()

    # Import data
    # bones
    bones_data_transform_raw = str(file_lines[0])
    bones_data_transform = ast.literal_eval(bones_data_transform_raw)

    # custom shapes
    bones_data_shape = None
    if len(file_lines) > 1:
        bones_data_shape_raw = str(file_lines[1])
        bones_data_shape = ast.literal_eval(bones_data_shape_raw)

    file.close()
    
    rig = None    
    if bpy.context.active_object:
        rig = get_object(bpy.context.active_object.name)
    
    # Set
    #   ref bones transforms
    if len(bones_data_transform) > 0 and import_ref_bones:
        print("Import ref bones tranforms...")
        
        bpy.ops.object.mode_set(mode='EDIT')
        
        sel_bones_names = [i.name for i in bpy.context.selected_editable_bones]
        
        for bname in bones_data_transform:
            ebone = get_edit_bone(bname)
            
            if ebone == None:
                continue
                
            if selection_only:
                if not bname in sel_bones_names:
                    continue

            if ebone.layers[17]:# ref bone only
                ebone.head, ebone.tail, ebone.roll = bones_data_transform[bname]

    #   custom shapes
    if bones_data_shape and import_custom_shapes:     
        print("Import custom shapes...")
        bpy.ops.object.mode_set(mode='POSE')
        
        sel_bones_names = [i.name for i in bpy.context.selected_pose_bones]

        for bone_name in bones_data_shape:
            if selection_only:
                if not bone_name in sel_bones_names:
                    continue
                    
            shape_name = bones_data_shape[bone_name][0]
            cs_obj = bpy.data.objects.get(shape_name)

            # create mesh data
            verts, edges, faces = bones_data_shape[bone_name][1], bones_data_shape[bone_name][2], bones_data_shape[bone_name][3]
            new_mesh_data = create_mesh_data(shape_name, verts, edges, faces)

            if cs_obj == None:# create cs obj if does not exist yet and set mesh data
                cs_obj = bpy.data.objects.new(shape_name, new_mesh_data)
            else:# if already exists, just replace mesh data
                cs_obj.data = new_mesh_data

            # set in collection
            col_rig = get_rig_collection(rig)
            col_master = get_master_collection(col_rig)
            cs_collec = get_cs_collection(col_master)            
           
            if cs_collec == None:
                cs_collec = bpy.data.collections.new("cs_grp")
                bpy.context.collection.children.link(cs_collec)
            try:
                cs_collec.objects.link(cs_obj)
            except:
                pass

            # parent to cs_grp
            cs_grp_obj = None
            for ob in cs_collec.objects:
                if ob.name.startswith("cs_grp"):
                    cs_grp_obj = ob
                    break

            cs_obj.parent = cs_grp_obj

            # hide it
            hide_object(cs_obj)

            # set the custom shape
            pbone = get_pose_bone(bone_name)
            if pbone == None:
                continue
            
            pbone.custom_shape = cs_obj
            if len(bones_data_shape[bone_name]) >= 5:
                shape_scale = bones_data_shape[bone_name][4]
                
                set_custom_shape_scale(pbone, shape_scale)


    restore_current_mode(cur_mode)


def _clean_scene(self):
    # Get collections in the active scene
    scene_collections = []

    # recursive function
    def get_children(collec):
        if len(collec.children) > 0:
            for collec_child in collec.children:
                scene_collections.append(collec_child)
                get_children(collec_child)

    for col in bpy.context.scene.collection.children:
        scene_collections.append(col)
        get_children(col)

    print("Scene Collections", scene_collections)

    # Delete collections that are not linked to the scene
    meshes_data = []
    count_deleted_col = 0
    count_deleted_objects = 0

    for _col in bpy.data.collections:
        if _col in scene_collections:  # the collection is linked to the scene, do not delete
            continue
        if _col.library:  # it's a linked collection, do not delete
            continue
        # remove objects in the collection
        print("removing collection", _col.name, _col.is_library_indirect)
        for obj in _col.objects:
            # first check that they're not in any other valid collection
            object_to_delete = True
            for col in obj.users_collection:
                if col in scene_collections:
                    object_to_delete = False
                    break
            if not object_to_delete:
                continue
            if obj.data:
                meshes_data.append(obj.data.name)
            bpy.data.objects.remove(obj, do_unlink=True)
            count_deleted_objects += 1
        # remove the collection
        bpy.data.collections.remove(_col)
        count_deleted_col += 1

    # remove unused meshes
    for data_name in meshes_data:
        current_mesh = bpy.data.meshes.get(data_name)
        if current_mesh:
            bpy.data.meshes.remove(current_mesh, do_unlink=True, do_id_user=True, do_ui_user=True)

    # remove orphan rig objects
    for obj in bpy.data.objects:
        if len(obj.users_collection) == 0:
            if obj.type == "ARMATURE":
                if obj.data.bones.get("c_pos") == None:
                    continue
                bpy.data.objects.remove(obj, do_unlink=True)
                count_deleted_objects += 1
                break

            if obj.name.startswith("cs_"):
                bpy.data.objects.remove(obj, do_unlink=True)
                count_deleted_objects += 1

    if count_deleted_col > 0 or count_deleted_objects > 0:
        self.report({'INFO'},
                    "Removed: " + str(count_deleted_col) + " collections, " + str(count_deleted_objects) + " objects")
    else:
        self.report({'INFO'}, "Clean scene, nothing to do.")


def _update_armature(self, context, required=False):
    print("\nUpdating armature...............................................................")
    sides = ['.l', '.r']
    '''
    # give an ID to all armatures
    for obj in bpy.data.objects:
        id = 0
        if obj.type == "ARMATURE":
            if "arp_rig_type" in obj.data.keys() and "rig_id" in obj.data.keys():
                if obj.data['rig_id'] == '':
                    obj.data['rig_id'] = str(id)
                    
    '''
    sel_armature = get_object(bpy.context.active_object.name)
    
    # save X-Mirror state
    xmirror_state = sel_armature.data.use_mirror_x
    sel_armature.data.use_mirror_x = False
    
    # Active all layers
    layers_select = enable_all_armature_layers()

    # Multi limb support
    limb_sides.get_multi_limbs()
    arm_sides = limb_sides.arm_sides
    leg_sides = limb_sides.leg_sides
    head_sides = limb_sides.head_sides

    bpy.ops.object.mode_set(mode='POSE')    
    
    axes_consistent_updated = False      
    
    if self.update_axes_consistent:
        print("make axes consistent...")
        
        # Consistent axes update, Z up for feet and arms
        # update feet constraints
        cs_arrow_inverted = False
        
        for leg_side in leg_sides:     
            
            bpy.ops.object.mode_set(mode='POSE')    
            
            c_foot_heel_name = ard.leg_bones_dict['foot']['foot_heel'] + leg_side
            c_foot_heel = get_pose_bone(c_foot_heel_name)
            changed_dir = False
            
            if c_foot_heel:
                cns1 = c_foot_heel.constraints.get('Transformation')
                if cns1:
                    if leg_side.endswith('.l'):
                        if cns1.to_min_x_rot > 0:
                            cns1.to_min_x_rot *= -1
                            changed_dir = True
                        if cns1.to_max_x_rot < 0:   
                            cns1.to_max_x_rot *= -1
                    elif leg_side.endswith('.r'):
                        if cns1.to_min_x_rot < 0:
                            cns1.to_min_x_rot *= -1
                            changed_dir = True
                        if cns1.to_max_x_rot > 0:
                            cns1.to_max_x_rot *= -1
                    
                cns2 = c_foot_heel.constraints.get('Limit Rotation')
                if cns2:
                    cns2.min_x = 0.0
                    cns2.max_x = 360.0
                    
            
            if changed_dir:
                # feet shapes              
                cs_foot = get_object('cs_foot'+leg_side)
                if cs_foot:
                    for v in cs_foot.data.vertices:
                        v.co[0] *= -1                        
                
                # arrow twist shapes
                if cs_arrow_inverted == False:  
                    cs_arrow = get_object('cs_arrow_twist')
                    if cs_arrow:                      
                        for v in cs_arrow.data.vertices:                       
                            v.co[2] *= -1
                        cs_arrow_inverted = True
                    
                # rotate heel ref Z axis
                bpy.ops.object.mode_set(mode='EDIT')
                
                foot_heel_names = [ard.leg_ref_bones_dict['heel'], ard.leg_ref_bones_dict['heel_bank_01'], ard.leg_ref_bones_dict['heel_bank_02']]
                for foot_heel_name in foot_heel_names:
                    foot_heel = get_edit_bone(foot_heel_name+leg_side)
                    foot_heel.roll += radians(180)   
                 
                bpy.ops.object.mode_set(mode='POSE')            
            
                        
        # arms Z up
        for arm_side in arm_sides:
            # update IK constraints pole angle
            ik_nostr_name = ard.arm_bones_dict['forearm']['ik_nostr'] + arm_side
          
            for dr in sel_armature.animation_data.drivers:
                if dr.data_path == 'pose.bones["'+ik_nostr_name+'"].constraints["IK"].pole_angle':
                    print('  adding 180° IK angle offset')
                    if not '2.0' in dr.driver.expression:
                        dr.driver.expression += '+ 2.0'
                        
            # shoulder shapes        
            cs_shoulder = get_object('cs_user_c_shoulder'+arm_side)
            if cs_shoulder:
                for v in cs_shoulder.data.vertices:
                    v.co[0] *= -1    
                    v.co[2] *= -1
                        
            bpy.ops.object.mode_set(mode='EDIT')
            
            # rotate shoulder ref Z axis
            shoulder_ref_name = ard.arm_ref_dict['shoulder']+arm_side
            shoulder_ref = get_edit_bone(shoulder_ref_name)
            shoulder_ref.roll += radians(180)
            
            bpy.ops.object.mode_set(mode='POSE')
                    
                        
        axes_consistent_updated = True
        # update tag
        sel_armature.data["arp_updated"] = '3.63.19'
        
    if axes_consistent_updated:
        up_feature = 'Axes updated for foot and arm bones (Z Up)'
        self.updated_features.append(up_feature)
                    
                    
    # better ankle twist constraints
    ankle_twist_updated = False
    
    if bpy.app.version >= (2,82,0):
        for leg_side in leg_sides:       
            leg_twist_name = ard.leg_bones_dict['calf']['twist'] + leg_side
            leg_twist = get_pose_bone(leg_twist_name)
            if leg_twist:
                locked_track_cns = leg_twist.constraints.get('Locked Track')
                if locked_track_cns:
                    # add Copy Rot cns
                    copy_rot_cns = leg_twist.constraints.new('COPY_ROTATION')
                    copy_rot_cns.name = 'Copy Rotation'
                    copy_rot_cns.target = sel_armature
                    copy_rot_cns.subtarget = locked_track_cns.subtarget
                    
                    # remove old Locked Track cns
                    leg_twist.constraints.remove(locked_track_cns)
                    
                    # move up Copy Rot
                    move_constraint(leg_twist, copy_rot_cns, 'UP', len(leg_twist.constraints))
                    
                    # set Stretch To to Swing rotation
                    cns_stretch = leg_twist.constraints.get('Stretch To')
                    if cns_stretch:                   
                        cns_stretch.keep_axis = 'SWING_Y'
                        
                    ankle_twist_updated = True
                    
    if ankle_twist_updated:
        up_feature = 'Improved ankle twist with better constraints'
        self.updated_features.append(up_feature)
                    
            
    # set neck_ref bbones segments to 1, no reasons to be higher
    neck_bbones_seg_updated = False

    for side in head_sides:
        neck_ref = get_pose_bone("neck_ref"+side)

        if neck_ref == None:
            continue

        if neck_ref.bone.bbone_segments != 1:
            neck_ref.bone.bbone_segments = 1
            neck_bbones_seg_updated = True

    if neck_bbones_seg_updated:
        up_feature = 'Set neck_ref reference bones to 1 bbone segment'
        self.updated_features.append(up_feature)

    # the thigh FK controller were set with multiple bendy bones segments, leads to silly error when keyframing, set to 1 instead
    controller_bbones_seg_updated = False

    for leg_side in leg_sides:
        c_thigh_fk = get_pose_bone("c_thigh_fk" + leg_side)

        if c_thigh_fk == None:
            continue

        if c_thigh_fk.bone.bbone_segments != 1:
            c_thigh_fk.bone.bbone_segments = 1
            controller_bbones_seg_updated = True


    for spine_name in ["c_root_master.x", "c_root.x", "c_spine_01.x", "c_spine_02.x", "c_spine_03.x"]:
        spine_bone = get_pose_bone(spine_name)

        if spine_bone == None:
            continue

        if spine_bone.bone.bbone_segments != 1:
            spine_bone.bone.bbone_segments = 1
            controller_bbones_seg_updated = True


    if controller_bbones_seg_updated:
        up_feature = 'Set arm and leg controllers to 1 bbone segment'
        self.updated_features.append(up_feature)


    # remove arp_layer properties, no more used
    removed_arp_layer_tot = 0
    for b in sel_armature.pose.bones:
        if "arp_layer" in b.keys():
            removed_arp_layer_tot += 1
            del b["arp_layer"]

    if removed_arp_layer_tot > 0:
        up_feature = 'Removed '+str(removed_arp_layer_tot)+' obsolete "arp_layer" properties'
        self.updated_features.append(up_feature)
        print(up_feature)


    # remove obsolete armature object properties
    obsolete_prop_tot = 0

    for prop_name in ['head_lock_obj', 'rig_breast', 'rig_ears', 'rig_facial', 'rig_index', 'rig_middle', 'rig_pinky', 'rig_ring', 'rig_tail', 'rig_thumb', 'rig_toes', 'rig_toes_index', 'rig_toes_middle', 'rig_toes_pinky', 'rig_toes_ring', 'rig_toes_thumb', 'symetric_fingers', 'symetric_toes']:
        if prop_name in sel_armature.keys():
            obsolete_prop_tot += 1
            del sel_armature[prop_name]

    if obsolete_prop_tot > 0:
        up_feature = 'Removed '+str(obsolete_prop_tot)+' other obsolete properties'
        self.updated_features.append(up_feature)
        print(up_feature)


    # set custom properties overridable
    properties_overridable_set = []
    for b in sel_armature.pose.bones:
        for prop_name in b.keys():
            if prop_name != "_RNA_UI":
                if not b.is_property_overridable_library('["'+prop_name+'"]'):
                    b.property_overridable_library_set('["'+prop_name+'"]', True)
                    if not prop_name in properties_overridable_set:
                        properties_overridable_set.append(prop_name)

    if len(properties_overridable_set):
        tot = len(properties_overridable_set)
        up_feature = "Set "+str(tot)+" properties as overridable (for rig linking)"
        self.updated_features.append(up_feature)
        print(up_feature)

    bpy.ops.object.mode_set(mode='EDIT')

    # Add a twist extra offset property for legs
    update_arms_legs_twist = False
    update_legs_stretchto_rot = False

    for leg_side in leg_sides:
        bpy.ops.object.mode_set(mode='POSE')

        twt_bname = "thigh_twist"+leg_side
        twt_pbone = get_pose_bone(twt_bname)
        c_thighb_name = "c_thigh_b"+leg_side
        c_thighb = get_pose_bone(c_thighb_name)

        # add prop
        prop_twist_name = "thigh_twist"
        if not prop_twist_name in c_thighb.keys():
            create_custom_prop(node=c_thighb, prop_name=prop_twist_name, prop_val=0.0, prop_min=-6.0, prop_max=6.0, prop_description="Tweak thigh twist offset")
            update_arms_legs_twist = True

        # add driver
        add_driver_to_prop(sel_armature, 'pose.bones["'+twt_bname+'"].rotation_euler', 'pose.bones["'+c_thighb_name+'"]["'+prop_twist_name+'"]', array_idx=1, exp="var")

        # set rotation mode
        twt_pbone.rotation_mode = "XYZ"

        # set transforms lock
        for i in range(0, 3):
            twt_pbone.lock_location[i] = True
            twt_pbone.lock_rotation[i] = True
            twt_pbone.lock_scale[i] = True

        # set thigh_twist StretchTo constraint to Swing rotation mode
        for cns in twt_pbone.constraints:
            if cns.type == "STRETCH_TO":
                if bpy.app.version >= (2,82,0):
                    if cns.keep_axis != "SWING_Y":
                        cns.keep_axis = "SWING_Y"
                        update_legs_stretchto_rot = True


    # Add a twist extra offset property for arms
    for arm_side in arm_sides:
        bpy.ops.object.mode_set(mode='EDIT')

        twt_bname = "arm_twist"+arm_side
        twist_bone = get_edit_bone(twt_bname)

        if twist_bone == None:
            continue

        twt_twk_bname = "arm_twist_twk"+arm_side
        twist_twk_bone = get_edit_bone(twt_twk_bname)
        shoulder_name = "shoulder"+arm_side
        shoulder_bone = get_edit_bone(shoulder_name)
        c_shoulder_name = "c_shoulder"+arm_side
        c_shoulder_bone = get_edit_bone(c_shoulder_name)

        # add helper bone
        if twist_twk_bone == None:
            twist_twk_bone = sel_armature.data.edit_bones.new(twt_twk_bname)
            copy_bone_transforms(twist_bone, twist_twk_bone)
            twist_twk_bone.parent = shoulder_bone
            set_bone_layer(twist_twk_bone, 11)
            twist_twk_bone.use_deform = False

        bpy.ops.object.mode_set(mode='POSE')

        twist_pbone = get_pose_bone(twt_bname)
        twist_twk_pbone = get_pose_bone(twt_twk_bname)
        c_shoulder_pbone = get_pose_bone(c_shoulder_name)

        # add prop
        prop_twist_name = "arm_twist"
        if not prop_twist_name in c_shoulder_pbone.keys():
            create_custom_prop(node=c_shoulder_pbone, prop_name=prop_twist_name, prop_val=0.0, prop_min=-6.0, prop_max=6.0, prop_description="Tweak arm twist offset")
            update_arms_legs_twist = True

        # set rotation mode
        twist_twk_pbone.rotation_mode = "XYZ"

        # set transforms lock
        for i in range(0, 3):
            twist_twk_pbone.lock_location[i] = True
            twist_twk_pbone.lock_rotation[i] = True
            twist_twk_pbone.lock_scale[i] = True

        # add driver
        add_driver_to_prop(sel_armature, 'pose.bones["'+twt_twk_bname+'"].rotation_euler', 'pose.bones["'+c_shoulder_name+'"]["'+prop_twist_name+'"]', array_idx=1, exp="var")

        # add constraint
        cns = twist_pbone.constraints.get("Copy Rotation")
        if cns == None:
            cns = twist_pbone.constraints.new("COPY_ROTATION")
            cns.target = bpy.context.active_object
            cns.subtarget = twt_twk_bname
            cns.use_x = cns.use_z = False
            cns.mix_mode = "AFTER"
            cns.target_space = cns.owner_space = "LOCAL"


    if update_arms_legs_twist:
        self.updated_features.append("Added twist tweak settings for arms and legs")
    if update_legs_stretchto_rot:
        self.updated_features.append("Improved thigh twist with better constraints")

    bpy.ops.object.mode_set(mode='POSE')

    # Update shape scales driver curves to linear interpolation, allows to select IK-FK controllers easier
    print("Updating IK-FK shape scale driver curves...")
    ik_fk_shape_linear_updated = False

    drivers_armature = [i for i in sel_armature.animation_data.drivers]
    blist = ["c_hand", "c_foot", "c_toes", "c_leg", "c_arm", "c_forearm", "c_thigh"]
    for dr in drivers_armature:
        for b in blist:
            need_update = False
            if dr.data_path.startswith('pose.bones["'+b) and dr.data_path.endswith('.custom_shape_scale'):
                # check current interpolation
                for key in dr.keyframe_points:
                    if key.interpolation == "CONSTANT":
                        need_update = True
                        break

                if not need_update:
                    continue

                ik_fk_shape_linear_updated = True
                # some drivers curves have "discrete values" preventing to set to interpolation to linear
                # to debug that, duplicate driver and remove original
                pb_name = get_pbone_name_from_data_path(dr.data_path)
                
                #print('  '+pb_name)
                driv_dict = drivers_to_dict(sel_armature, [get_pose_bone(pb_name)])
                sel_armature.animation_data.drivers.remove(dr)
                create_drivers_from_dict(driv_dict, obj=sel_armature, key_interpolation='LINEAR')

    if ik_fk_shape_linear_updated:
        up_feature = 'Set IK-FK scale shapes with linear interpolation'
        self.updated_features.append(up_feature)


    print("Optional updates done.")

    if 'dr' in locals():
        del dr

    remove_invalid_drivers()

    if required:
        update_34147 = False
        update_36118 = False
        update_30 = False

        if not 'arp_updated' in sel_armature.data.keys():
            update_34147 = True
            update_36118 = True
        elif sel_armature.data['arp_updated'] == '3.41.47':
            update_36118 = True

        if bpy.app.version >= (3,0,0):
            if not 'arp_updated_3.0' in sel_armature.data.keys():
                update_30 = True


        if update_34147:
            print("Updating 3.41.47...")
            drivers_armature = sel_armature.animation_data.drivers
            for dr in drivers_armature:
                if ("c_eyelid_top" in dr.data_path or "c_eyelid_bot" in dr.data_path) and ".scale" in dr.data_path:
                    for var in dr.driver.variables:
                        bone_name = var.targets[0].data_path.split('"')[1]
                        if 'pose.bones["c_eyelid_bot' in var.targets[0].data_path or 'pose.bones["c_eyelid_top' in var.targets[0].data_path:
                            print("Deleting eyelid driver:", bone_name)
                            sel_armature.animation_data.drivers.remove(dr)
                            break

            if 'dr' in locals():
                del dr

            # Clear the reference bones constraints
            bpy.ops.object.mode_set(mode='POSE')
            for b in bpy.context.active_object.pose.bones:
                if len(b.constraints) > 0:
                    if b.bone.layers[17] and "_ref" in b.name:
                        for cns in b.constraints:
                            b.constraints.remove(cns)

                            # Refresh the rig_add
            if bpy.context.active_object.arp_secondary_type == "ADDITIVE":
                rig_add = get_rig_add(sel_armature)
                if rig_add:
                    bpy.data.objects.remove(rig_add, do_unlink=True)

                rig_add = refresh_rig_add(sel_armature)
                copy_bones_to_rig_add(sel_armature, rig_add)
                print("\nRig add refreshed.")

            # Updating collections
            print("\nUpdating collections...")

            # assign to new collections, for rigs coming from Blender 2.79
            found_rig_collec = False
            rig_collec = ""
            found_cs_collec = False

            # are the rig and cs collections there?
            if len(sel_armature.users_collection) > 0:
                for col in sel_armature.users_collection:
                    if len(col.name.split('_')) > 1:
                        if col.name.split('_')[1] == 'rig':
                            found_rig_collec = True
                            rig_collec = col.name
                            print("    rig collection found:", col.name)

            cs_grp = get_object("cs_grp")

            if not cs_grp:
                print("No cs_grp object in the scene")
                bpy.data.objects.new("cs_grp", None)
                bpy.context.scene.collection.objects.link(bpy.data.objects["cs_grp"])
                print("cs_grp created")

            cs_grp = get_object("cs_grp")
            
            if len(cs_grp.users_collection) > 0:
                for col in cs_grp.users_collection:
                    if len(col.name.split('_')) > 1:
                        if col.name.split('_')[1] == 'cs':
                            found_cs_collec = True
                            print("    cs collection found:", col.name)

            # if only the rig collec is found, it's likely the obsolete "char_rig" group.
            # delete it
            if found_rig_collec and not found_cs_collec:
                print("    rig collection is actually the obsolete rig group, delete it.")
                bpy.data.collections.remove(bpy.data.collections[rig_collec])
                found_rig_collec = False

            if not found_rig_collec:
                print("    rig collection not found, creating...")
                collec_rig = bpy.data.collections.get("character1_rig")
                if not collec_rig:
                    collec_rig = bpy.data.collections.new("character1_rig")
                    bpy.context.scene.collection.children.link(collec_rig)
                    print("    new collection created:", collec_rig.name)

                # get the master parent
                master_parent = sel_armature
                reached_top = False
                while reached_top == False:
                    if master_parent.parent:
                        master_parent = master_parent.parent
                    else:
                        reached_top = True

                print("    rig master:", master_parent.name)

                # get the whole rig hierarchy
                rig_hierarchy = [master_parent]

                for obj in bpy.data.objects:
                    if obj.parent:
                        if obj.parent == master_parent:
                            rig_hierarchy.append(obj)

                            for _obj in rig_hierarchy:
                                for obj_1 in bpy.data.objects:
                                    if obj_1.parent:
                                        if obj_1.parent == _obj:
                                            rig_hierarchy.append(obj_1)

                for child in rig_hierarchy:
                    try:
                        collec_rig.objects.link(child)
                        #print("    linking child", child.name)
                    except:
                        #print(child.name, "is already in the collection", collec_rig.name)
                        pass

                    # remove from other collec
                    for _subcol in child.users_collection:
                        if _subcol != collec_rig:
                            _subcol.objects.unlink(child)
                    try:
                        bpy.context.scene.collection.objects.unlink(child)
                    except:
                        pass

            if not found_cs_collec:
                print("    cs collection not found, creating...")
                collec_cs = bpy.data.collections.get("character1_cs")
                if not collec_cs:
                    collec_cs = bpy.data.collections.new("character1_cs")
                    bpy.context.scene.collection.children.link(collec_cs)
                    print("    new collection created:", collec_cs.name)

                # get the master parent
                master_parent = bpy.data.objects["cs_grp"]

                # get the whole rig hierarchy
                cs_hierarchy = [master_parent]

                for obj in bpy.data.objects:
                    if obj.parent:
                        if obj.parent == master_parent:
                            cs_hierarchy.append(obj)

                for child in cs_hierarchy:
                    try:
                        collec_cs.objects.link(child)
                    except:
                        pass
                    # remove from other collec
                    for _subcol in child.users_collection:
                        if _subcol != collec_cs:
                            _subcol.objects.unlink(child)
                    try:
                        bpy.context.scene.collection.objects.unlink(child)
                    except:
                        pass

                # hide it
                collec_cs.hide_viewport = True
                collec_cs.hide_render = True

            # make sure the rig collections are children of a master rig collection
            collections_to_check = ["character1_rig", "character1_cs"]

            for col_name in collections_to_check:
                col = bpy.data.collections.get(col_name)
                if col:
                    for child_col in bpy.context.scene.collection.children:  # the collection is at the root level
                        if child_col == col:
                            master_col = bpy.data.collections.get("character1")

                            if not master_col:
                                new_col = bpy.data.collections.new("character1")
                                bpy.context.scene.collection.children.link(new_col)
                                print("    Created new collection:", "character1")

                            new_col.children.link(col)
                            bpy.context.scene.collection.children.unlink(col)


                            # delete obsolete "char_rig" group/collection from 2.79 files
            char_rig_collec = bpy.data.collections.get("char_rig")
            if char_rig_collec:
                print("    Delete collection", char_rig_collec.name)
                bpy.data.collections.remove(char_rig_collec)

            print("Collections updated.")



            def replace_var(dr):
                for v1 in dr.driver.variables:
                    if 'c_ikfk_arm' in v1.targets[0].data_path:
                        v1.targets[0].data_path = v1.targets[0].data_path.replace('c_ikfk_arm', 'c_hand_ik')

                    if 'c_ikfk_leg' in v1.targets[0].data_path:
                        v1.targets[0].data_path = v1.targets[0].data_path.replace('c_ikfk_leg', 'c_foot_ik')

            # Save current mode
            current_mode = bpy.context.mode

            # Clean drivers
            remove_invalid_drivers()

            bpy.ops.object.mode_set(mode='EDIT')           

            # disable the proxy picker to avoid bugs
            try:
                bpy.context.scene.Proxy_Picker.active = False
            except:
                pass

            need_update = False

            # Delete the disabled/hidden bones from previous versions
            found_facial = False
            found_neck = False
            found_legs = []
            found_arms = []
            for b in bpy.context.active_object.data.edit_bones:
                if b.layers[22] and not "_proxy" in b.name and not b.layers[17] and not b.layers[1]:
                    if b.name == "jaw_ref.x":
                        found_facial = True
                    if b.name == "c_neck.x":
                        found_neck = True
                    if "c_foot_ik" in b.name:
                        found_legs.append(b.name[-2:])
                    if "c_hand_ik" in b.name:
                        found_arms.append(b.name[-2:])

                    delete_edit_bone(b)


                    # remove other facial hidden bones
            if found_facial:
                set_facial(enable=False)

            if found_neck:
                for b in ard.neck_bones:
                    eb = get_edit_bone(b)
                    if eb:
                        delete_edit_bone(eb)

            if len(found_legs) > 0:
                for s in found_legs:
                    for b in ard.leg_bones_list:
                        eb = get_edit_bone(b + s)
                        if eb:
                            delete_edit_bone(eb)

            if len(found_arms) > 0:
                for s in found_arms:
                    for b in ard.arm_bones:
                        eb = get_edit_bone(b + s)
                        if eb:
                            delete_edit_bone(eb)

            bpy.ops.object.mode_set(mode='POSE')

            # create the ik_fk property if necessary (update from older armature version)
            c_foot_ik = get_pose_bone("c_foot_ik.l")
            if c_foot_ik:
                if len(c_foot_ik.keys()) > 0:
                    if not 'ik_fk_switch' in c_foot_ik.keys():
                        need_update = True

                        for side in leg_sides:
                            get_pose_bone("c_foot_ik" + side)["ik_fk_switch"] = get_pose_bone("c_ikfk_leg" + side)[
                                "ik_fk_switch"]
                            foot_ik = get_pose_bone("c_foot_ik" + side)

                            if get_prop_setting(foot_ik, 'ik_fk_switch', 'min') != 0.0 and get_prop_setting(foot_ik, 'ik_fk_switch', 'max') != 1.0:
                                set_prop_setting(foot_ik, 'ik_fk_switch', 'min', 0.0)
                                set_prop_setting(foot_ik, 'ik_fk_switch', 'max', 1.0)
                                set_prop_setting(foot_ik, 'ik_fk_switch', 'soft_min', 0.0)
                                set_prop_setting(foot_ik, 'ik_fk_switch', 'soft_max', 1.0)                              
                                print("Changed limits of foot IK FK Switch property")

                        for side in arm_sides:
                            get_pose_bone("c_hand_ik" + side)["ik_fk_switch"] = get_pose_bone("c_ikfk_arm" + side)[
                                "ik_fk_switch"]
                            hand_ik = get_pose_bone("c_hand_ik" + side)

                            if get_prop_setting(hand_ik, 'ik_fk_switch', 'min') != 0.0 and get_prop_setting(hand_ik, 'ik_fk_switch', 'max') != 1.0:
                                set_prop_setting(hand_ik, 'ik_fk_switch', 'min', 0.0)
                                set_prop_setting(hand_ik, 'ik_fk_switch', 'max', 1.0)
                                set_prop_setting(hand_ik, 'ik_fk_switch', 'soft_min', 0.0)
                                set_prop_setting(hand_ik, 'ik_fk_switch', 'soft_max', 1.0)
                                print("Changed limits of hand IK FK Switch property")

                        # update drivers
                        for obj in bpy.data.objects:
                            try:
                                drivers1 = obj.animation_data.drivers
                                drivers2 = bpy.context.active_object.data.animation_data.drivers

                                for dr in drivers1:
                                    replace_var(dr)

                                for dr in drivers2:
                                    replace_var(dr)

                            except:
                                pass
                        print('....IK-FK Drivers updated')

            # Update armature data drivers to pose bone (hide -> scale) to solve the dependency problem when linking the armature into a scene
            if bpy.context.active_object.data.animation_data:
                drivers2 = bpy.context.active_object.data.animation_data.drivers

                for dr in drivers2:
                    if ".hide" in dr.data_path:
                        # create the new ones on pose bones
                        new_dr = bpy.context.active_object.animation_data.drivers.from_existing(src_driver=dr)
                        dp = dr.data_path.replace("bones", "pose.bones")
                        dp = dp.replace(".hide", ".custom_shape_scale")
                        new_dr.data_path = dp

                        # invert the expression
                        if new_dr.driver.expression == "1-var":
                            new_dr.driver.expression = "var"
                        if new_dr.driver.expression == "var":
                            new_dr.driver.expression = "1-var"

                        dp_string = dr.data_path[7:]

                        # delete the old one
                        bpy.context.active_object.data.driver_remove(dr.data_path, -1)

                        # disable the hide
                        get_data_bone(dp_string.partition('"')[0]).hide = False

                    if "inherit_rotation" in dr.data_path:
                        try:
                            bpy.context.active_object.data.driver_remove(dr.data_path, -1)
                        except:
                            print("Unknown error when trying to delete a driver.")

            # Update shape scales driver curves... was set 0.1 instead of 1.0
            drivers_armature = bpy.context.active_object.animation_data.drivers
            blist = ["c_hand", "c_foot", "c_toes", "c_leg", "c_arm", "c_forearm", "c_thigh"]
            for dr in drivers_armature:
                for b in blist:
                    if b in dr.data_path and "custom_shape_scale" in dr.data_path:
                        for key in dr.keyframe_points:
                            if key.co[0] > 0.01 and key.co[0] < 0.99:
                                key.co[0] = 1.0
                                print("Updated driver curve of", dr.data_path)

            if 'dr' in locals():
                del dr

            # Update IK-FK constraints drivers, set the first constraints influence in the stack always to 1.0 for better blend between IK-FK
            for dr in drivers_armature:
                if 'constraints["rotIK"].influence' in dr.data_path or 'constraints["ik_rot"].influence' in dr.data_path:
                    if dr.driver.expression != "0":
                        dr.driver.expression = "0"  # 0 = 1 according to the driver curve
                        print("Updated driver expression of", dr.data_path)

                if 'constraints["locIK"].influence' in dr.data_path and (
                                '["forearm' in dr.data_path or '["leg' in dr.data_path):
                    if dr.driver.expression != "0":
                        dr.driver.expression = "0"
                        print("Updated driver expression of", dr.data_path)

            # Make sure properties limits are corrects
            for side in arm_sides:
                hand_ik = get_pose_bone("c_hand_ik" + side)
                if hand_ik:
                    if get_prop_setting(hand_ik, 'ik_fk_switch', 'min') != 0.0 and get_prop_setting(hand_ik, 'ik_fk_switch', 'max') != 1.0:
                        set_prop_setting(hand_ik, 'stretch_length', 'min', 0.2)
                        set_prop_setting(hand_ik, 'stretch_length', 'max', 4.0)
                        set_prop_setting(hand_ik, 'auto_stretch', 'min', 0.0)
                        set_prop_setting(hand_ik, 'auto_stretch', 'max', 1.0)
                        set_prop_setting(hand_ik, 'ik_fk_switch', 'min', 0.0)
                        set_prop_setting(hand_ik, 'ik_fk_switch', 'max', 1.0)                      
                        print('Properties limits  of arms set')

            for side in leg_sides:
                foot_ik = get_pose_bone("c_foot_ik" + side)
                if foot_ik:                    
                    if get_prop_setting(foot_ik, 'ik_fk_switch', 'min') != 0.0 and get_prop_setting(foot_ik, 'ik_fk_switch', 'max') != 1.0:
                        set_prop_setting(foot_ik, 'stretch_length', 'min', 0.2)
                        set_prop_setting(foot_ik, 'stretch_length', 'max', 4.0)
                        set_prop_setting(foot_ik, 'auto_stretch', 'min', 0.0)
                        set_prop_setting(foot_ik, 'auto_stretch', 'max', 1.0)
                        set_prop_setting(foot_ik, 'ik_fk_switch', 'min', 0.0)
                        set_prop_setting(foot_ik, 'ik_fk_switch', 'max', 1.0)                   
                        print('Properties limits of legs set')

            # Update arms and leg pole parent
            for side in leg_sides:
                if get_pose_bone("c_leg_pole" + side):
                    pole = get_pose_bone("c_leg_pole" + side)

                    # unparent
                    bpy.ops.object.mode_set(mode='EDIT')
                    get_edit_bone("c_leg_pole" + side).parent = None
                    bpy.ops.object.mode_set(mode='POSE')

                    # create the properties
                    if not "pole_parent" in pole.keys():
                        pole["pole_parent"] = 1
                        set_prop_setting(pole, 'pole_parent', 'min', 0)
                        set_prop_setting(pole, 'pole_parent', 'max', 1)
                        set_prop_setting(pole, 'pole_parent', 'default', 1)
                        set_prop_setting(pole, 'pole_parent', 'description', "Pole parent")                        
                       
                        # Create the constraints
                    cons = [None, None]
                    if len(pole.constraints) > 0:
                        for cns in pole.constraints:
                            if cns.name == "Child Of_local":
                                cons[0] = cns
                            if cns.name == "Child Of_global":
                                cons[1] = cns

                        if cons[0] == None:
                            cns1 = pole.constraints.new("CHILD_OF")
                            cns1.name = "Child Of_local"
                            cns1.target = bpy.context.active_object
                            cns1.subtarget = "c_foot_ik" + side

                        if cons[1] == None:
                            cns2 = pole.constraints.new("CHILD_OF")
                            cns2.name = "Child Of_global"
                            cns2.target = bpy.context.active_object
                            cns2.subtarget = get_first_master_controller()

                    else:
                        cns1 = pole.constraints.new("CHILD_OF")
                        cns1.name = "Child Of_local"
                        cns1.target = bpy.context.active_object
                        cns1.subtarget = "c_foot_ik" + side

                        cns2 = pole.constraints.new("CHILD_OF")
                        cns2.name = "Child Of_global"
                        cns2.target = bpy.context.active_object
                        cns2.subtarget = get_first_master_controller()

                    # Create drivers
                    dr1 = bpy.context.active_object.driver_add('pose.bones["c_leg_pole' + side + '"].constraints["Child Of_local"].influence', -1)
                    dr1.driver.expression = "var"
                    if len(dr1.driver.variables) == 0:
                        base_var = dr1.driver.variables.new()
                    else:
                        base_var = dr1.driver.variables[0]
                    base_var.type = 'SINGLE_PROP'
                    base_var.name = 'var'
                    base_var.targets[0].id = bpy.context.active_object
                    base_var.targets[0].data_path = 'pose.bones["c_leg_pole' + side + '"].["pole_parent"]'

                    dr2 = bpy.context.active_object.driver_add('pose.bones["c_leg_pole' + side + '"].constraints["Child Of_global"].influence', -1)
                    dr2.driver.expression = "1 - var"
                    if len(dr2.driver.variables) == 0:
                        base_var = dr2.driver.variables.new()
                    else:
                        base_var = dr2.driver.variables[0]
                    base_var.type = 'SINGLE_PROP'
                    base_var.name = 'var'
                    base_var.targets[0].id = bpy.context.active_object
                    base_var.targets[0].data_path = 'pose.bones["c_leg_pole' + side + '"].["pole_parent"]'

            for side in arm_sides:
                if get_pose_bone("c_arms_pole" + side):
                    pole = get_pose_bone("c_arms_pole" + side)

                    # unparent
                    bpy.ops.object.mode_set(mode='EDIT')
                    get_edit_bone("c_arms_pole" + side).parent = None
                    bpy.ops.object.mode_set(mode='POSE')

                    # create the properties
                    if not "pole_parent" in pole.keys():  
                        create_custom_prop(node=pole, prop_name="pole_parent", prop_val=1, prop_min=0, prop_max=1, prop_description="Pole parent", soft_min=None, soft_max=None, default=None)                             

                    #   Create the constraints
                    cons = [None, None]
                    if len(pole.constraints) > 0:
                        for cns in pole.constraints:
                            if cns.name == "Child Of_local":
                                cons[0] = cns
                            if cns.name == "Child Of_global":
                                cons[1] = cns

                        if cons[0] == None:
                            cns1 = pole.constraints.new("CHILD_OF")
                            cns1.name = "Child Of_local"
                            cns1.target = bpy.context.active_object
                            cns1.subtarget = "c_root_master.x"

                        if cons[1] == None:
                            cns2 = pole.constraints.new("CHILD_OF")
                            cns2.name = "Child Of_global"
                            cns2.target = bpy.context.active_object
                            cns2.subtarget = get_first_master_controller()

                    else:
                        cns1 = pole.constraints.new("CHILD_OF")
                        cns1.name = "Child Of_local"
                        cns1.target = bpy.context.active_object
                        cns1.subtarget = "c_root_master.x"

                        cns2 = pole.constraints.new("CHILD_OF")
                        cns2.name = "Child Of_global"
                        cns2.target = bpy.context.active_object
                        cns2.subtarget = get_first_master_controller()

                    # Create drivers
                    dr1 = bpy.context.active_object.driver_add('pose.bones["c_arms_pole' + side + '"].constraints["Child Of_local"].influence', -1)
                    dr1.driver.expression = "var"
                    if len(dr1.driver.variables) == 0:
                        base_var = dr1.driver.variables.new()
                    else:
                        base_var = dr1.driver.variables[0]
                    base_var.type = 'SINGLE_PROP'
                    base_var.name = 'var'
                    base_var.targets[0].id = bpy.context.active_object
                    base_var.targets[0].data_path = 'pose.bones["c_arms_pole' + side + '"].["pole_parent"]'

                    dr2 = bpy.context.active_object.driver_add('pose.bones["c_arms_pole' + side + '"].constraints["Child Of_global"].influence', -1)
                    dr2.driver.expression = "1 - var"
                    if len(dr2.driver.variables) == 0:
                        base_var = dr2.driver.variables.new()
                    else:
                        base_var = dr2.driver.variables[0]
                    base_var.type = 'SINGLE_PROP'
                    base_var.name = 'var'
                    base_var.targets[0].id = bpy.context.active_object
                    base_var.targets[0].data_path = 'pose.bones["c_arms_pole' + side + '"].["pole_parent"]'

            # Update Fingers Grasp
            for side in arm_sides:
                c_hand_fk = get_pose_bone("c_hand_fk" + side)
                if c_hand_fk:
                    if not 'fingers_grasp' in c_hand_fk.keys():                 
                        print("Adding Fingers Grasp...")

                        # create properties
                        create_custom_prop(node=c_hand_fk, prop_name='fingers_grasp', prop_val=0.0, prop_min=-1.0, prop_max=2.0, prop_description="Fingers grasp (bend all fingers)", soft_min=None, soft_max=None)
                        
                        # create drivers
                        drivers_armature = bpy.context.active_object.animation_data.drivers
                        fingers_bend_all = ["thumb_bend_all", "index_bend_all", "middle_bend_all", "ring_bend_all",
                                            "pinky_bend_all"]

                        for driver in drivers_armature:
                            for finger in fingers_bend_all:
                                if (finger + side) in driver.data_path:
                                    dr = driver.driver
                                    if 'thumb' in finger:
                                        dr.expression = "-var - (var_001 * 0.5)"
                                    else:
                                        dr.expression = "-var - var_001"
                                    base_var = dr.variables[0]
                                    new_var = dr.variables.new()
                                    new_var.type = 'SINGLE_PROP'
                                    new_var.name = 'var_001'
                                    new_var.targets[0].id = base_var.targets[0].id
                                    new_var.targets[0].data_path = 'pose.bones["c_hand_fk' + side + '"]["fingers_grasp"]'


                                    # Update fingers rotation constraints to fix the cyclic dependency issue
            fingers_rot = ['c_thumb1_rot', 'c_index1_rot', 'c_middle1_rot', 'c_ring1_rot', 'c_pinky1_rot']

            for side in arm_sides:
                for f in fingers_rot:
                    finger_rot = get_pose_bone(f + side)
                    if finger_rot:
                        if len(finger_rot.constraints) > 0:
                            for cns in finger_rot.constraints:
                                if cns.type == "COPY_ROTATION":
                                    print("Deleting", cns.name, "from", finger_rot.name)
                                    finger_rot.constraints.remove(cns)

                                    new_finger_rot = get_pose_bone(f.split('_')[1] + side)
                                    if new_finger_rot:
                                        print("Adding new Copy Rot constraint to", new_finger_rot.name)
                                        new_cns = new_finger_rot.constraints.new("COPY_ROTATION")
                                        new_cns.target = bpy.context.active_object
                                        new_cns.subtarget = f.split('_')[1].replace('1', '') + '_bend_all' + side
                                        new_cns.use_x = True
                                        new_cns.use_y = new_cns.use_z = False
                                        new_cns.target_space = 'LOCAL'
                                        new_cns.owner_space = 'LOCAL'

                                    break

            # Update spine bones relationships
            spine_bones_to_update = ["spine_02.x", "spine_03.x"]

            for bname in spine_bones_to_update:

                bpy.ops.object.mode_set(mode='EDIT')

                # make sure the child bone exist
                bone_parent = get_edit_bone('c_' + bname)
                ebone = get_edit_bone(bname)
                if bone_parent and ebone == None:
                    print(bname, "not found. Create it...")
                    ebone = bpy.context.active_object.data.edit_bones.new(bname)
                    ebone.head, ebone.tail, ebone.roll = bone_parent.head.copy(), bone_parent.tail.copy(), bone_parent.roll
                    print("   created.")
                    # set layer
                    print("   set layer...")
                    ebone.layers[8] = True
                    for i, lay in enumerate(ebone.layers):
                        if i != 8 and i != 31:
                            ebone.layers[i] = False
                    print("   layers set")
                    print("   done.")

                # change parent
                if ebone:
                    bone_parent = get_edit_bone('c_' + bname)
                    if not bone_parent:
                        continue
                    else:
                        if ebone.parent != bone_parent:
                            ebone.use_connect = False
                            ebone.parent = bone_parent
                            print("Changed spine bone parent:", ebone.name, "parent to", bone_parent.name)


                            # clear constraints
                bpy.ops.object.mode_set(mode='POSE')
                pbone = get_pose_bone(bname)
                if pbone:
                    pbone.bone.use_inherit_rotation = True
                    if len(pbone.constraints) > 0:
                        for cns in pbone.constraints:
                            pbone.constraints.remove(cns)
                            print("Removed constraint", pbone.name)

                bpy.ops.object.mode_set(mode='EDIT')

            bpy.ops.object.mode_set(mode='POSE')

            # Update Auto Eyelids
            for side in sides:
                eyeb = get_pose_bone("c_eye" + side)
                if eyeb:
                    if len(eyeb.keys()) > 0:
                        if not 'auto_eyelid' in eyeb.keys():
                            print("auto-eyelid prop not found, updating...")
                            # ensure constraints
                            cns = get_pose_bone("c_eyelid_base" + side).constraints[0]
                            enable_constraint(cns, True)                           
                            cns.use_x = cns.use_y = cns.use_z = True

                            # create prop
                            create_custom_prop(node=eyeb, prop_name='auto_eyelid', prop_val=0.1, prop_min=0.0, prop_max=1.0, prop_description='Automatic eyelid rotation from the eye')                     

                            # create drivers
                            dr = bpy.context.active_object.driver_add('pose.bones["' + "c_eyelid_base" + side + '"].constraints["Copy Rotation"].influence', -1)
                            dr.driver.expression = "var"
                            base_var = dr.driver.variables.new()
                            base_var.type = 'SINGLE_PROP'
                            base_var.name = 'var'
                            base_var.targets[0].id = bpy.context.active_object
                            base_var.targets[0].data_path = 'pose.bones["' + eyeb.name + '"]["auto_eyelid"]'

                            print("Updated.")

            # Fix arm pre_pole constraint type
            for bone in bpy.context.active_object.pose.bones:
                if 'fk_pre_pole' in bone.name:
                    for cns in bone.constraints:
                        if cns.type == 'TRACK_TO':
                            print("Obsolete pre_pole arm constraint found, updating...")
                            new_cns = bone.constraints.new('DAMPED_TRACK')
                            new_cns.target = cns.target
                            new_cns.subtarget = cns.subtarget
                            bone.constraints.remove(cns)
                            print("Updated.")

            # Hide/delete obsolete ref bones - Pose mode only
            for side in leg_sides:
                toes_end_ref = get_data_bone('toes_end_ref' + side)
                if toes_end_ref:
                    if toes_end_ref.hide == False:
                        toes_end_ref.hide = True
                        print("Obsolete toes_end_ref" + side, " has been hidden.")

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

            try:
                for side in arm_sides:
                    get_edit_bone('c_ikfk_arm' + side).select = True
                for side in leg_sides:
                    get_edit_bone('c_ikfk_leg' + side).select = True
                    need_update = True

                bpy.ops.armature.delete()
                print('....Deleted deprecated bones')
            except:
                pass

            def add_ik_ctrl(ik_limb_ctrl, limb_nostr, limb2_nostr, ikfk_bone, side):
                if bpy.context.active_object.data.edit_bones.get(ik_limb_ctrl + side) == None:
                    # Create bone
                    new_ebone = bpy.context.active_object.data.edit_bones.new(ik_limb_ctrl + side)
                    new_ebone.head = get_edit_bone(limb_nostr + side).head.copy()
                    new_ebone.tail = get_edit_bone(limb_nostr + side).tail.copy()
                    new_ebone.roll = get_edit_bone(limb_nostr + side).roll

                    # Set parent
                    if "thigh" in ik_limb_ctrl:
                        new_ebone.parent = get_edit_bone("c_thigh_b" + side)
                    if "arm" in ik_limb_ctrl:
                        new_ebone.parent = get_edit_bone("c_shoulder" + side)

                    bpy.ops.object.mode_set(mode='POSE')

                    # Set shape
                    new_pbone = get_pose_bone(ik_limb_ctrl + side)
                    try:
                        new_pbone.custom_shape = bpy.data.objects["cs_box"]
                    except:
                        print("Could not set the shape of " + ik_limb_ctrl + " bone")
                    new_pbone.custom_shape_transform = get_pose_bone(limb_nostr + side)
                    new_pbone.bone.show_wire = True

                    # Lock transforms
                    for i in range(0, 3):
                        new_pbone.lock_location[i] = True
                        new_pbone.lock_scale[i] = True
                        new_pbone.lock_rotation[i] = True

                    new_pbone.lock_rotation[1] = False

                    # Set axis order
                    new_pbone.rotation_mode = "ZXY"

                    # Set layer
                    set_in_layer = 0
                    if get_pose_bone(ikfk_bone + side):
                        if get_pose_bone(ikfk_bone + side).bone.layers[22]:
                            print(ikfk_bone + side, "is disabled")
                            set_in_layer = 22

                    for layer in range(0, 32):
                        if layer == set_in_layer:
                            new_pbone.bone.layers[layer] = True
                        else:
                            new_pbone.bone.layers[layer] = False


                            # Disable deform
                    new_pbone.bone.use_deform = False

                    # Set color group
                    try:
                        new_pbone.bone_group = bpy.context.active_object.pose.bone_groups["body" + side[-2:]]
                    except:
                        print('Bone group "body' + side[-2:] + ' not found')

                    # Create driver
                    dr = bpy.context.active_object.driver_add('pose.bones["' + limb2_nostr + side + '"].constraints["IK"].pole_angle', -1)
                    dr.driver.expression = "var"
                    if len(dr.driver.variables) == 0:
                        base_var = dr.driver.variables.new()
                    else:
                        base_var = dr.driver.variables[0]
                    base_var.type = 'SINGLE_PROP'
                    base_var.name = 'var'
                    base_var.targets[0].id = bpy.context.active_object
                    base_var.targets[0].data_path = 'pose.bones["' + ik_limb_ctrl + side + '"].rotation_euler[1]'

                    # Set cyclic fcurve
                    dr.modifiers.remove(dr.modifiers[0])
                    # +180 angle for right side = offset -2 on X axis
                    val = 0.0
                    if side[-2:] == ".r":
                        val = -2.0

                    keyf1 = dr.keyframe_points.insert(-2.0 - val, radians(-180))
                    keyf1.interpolation = 'LINEAR'
                    keyf2 = dr.keyframe_points.insert(2.0 - val, radians(180))
                    keyf2.interpolation = 'LINEAR'
                    dr.modifiers.new("CYCLES")

                    # Create driver to hide the bone in FK mode
                    new_dr = bpy.context.active_object.driver_add('pose.bones["' + ik_limb_ctrl + side + '"].custom_shape_scale', -1)
                    new_dr.driver.expression = "1-var"
                    base_var = new_dr.driver.variables.new()
                    base_var.type = 'SINGLE_PROP'
                    base_var.name = 'var'
                    base_var.targets[0].id = bpy.context.active_object
                    base_var.targets[0].data_path = 'pose.bones["' + ikfk_bone + side + '"]["ik_fk_switch"]'

                    # Set curve
                    new_dr.modifiers.remove(new_dr.modifiers[0])
                    keyf1 = new_dr.keyframe_points.insert(0.0, 0.0)
                    keyf1.interpolation = 'CONSTANT'
                    keyf2 = new_dr.keyframe_points.insert(1.0, 1.0)
                    keyf2.interpolation = 'CONSTANT'

                    # Add arp_layer
                    new_pbone["arp_layer"] = 0
                    new_pbone.bone["arp_layer"] = 0

                    bpy.ops.object.mode_set(mode='EDIT')

                    # Add proxy bone
                    # load custom shape meshes if necessary
                    if bpy.data.objects.get("rig_ui"):
                        if bpy.data.objects.get("cs_ctrl_ik_solid_red") == None:
                            obj_to_append = ["cs_ctrl_ik_solid_red", "cs_ctrl_ik_solid_blue", "cs_ctrl_ik_solid_red_sel",
                                             "cs_ctrl_ik_solid_blue_sel"]
                            append_from_arp(nodes=obj_to_append, type="object")

                            # parent it to the "cs_grp" object
                            for obj in obj_to_append:
                                if bpy.data.objects.get("cs_grp"):
                                    bpy.data.objects[obj].parent = bpy.data.objects["cs_grp"]
                                else:
                                    print("Could not find the cs_grp object to parent to")
                            print("Appended cs_ctrl_ik shapes")

                    if "thigh" in ik_limb_ctrl:
                        pole_picker = get_edit_bone("c_leg_pole_proxy" + side)
                        limb_picker = get_edit_bone("c_thigh_fk_proxy" + side)

                    if "arm" in ik_limb_ctrl:
                        pole_picker = get_edit_bone("c_arms_pole_proxy" + side)
                        limb_picker = get_edit_bone("c_arm_fk_proxy" + side)

                    if pole_picker and limb_picker:
                        # Create bone
                        print("Creating picker bone:", ik_limb_ctrl + "_proxy" + side)
                        b = bpy.context.active_object.data.edit_bones.new(ik_limb_ctrl + "_proxy" + side)

                        b.head = limb_picker.head
                        b.tail = limb_picker.tail
                        b.head += limb_picker.z_axis.normalized() * (limb_picker.tail - limb_picker.head).magnitude * 1.5
                        b.tail += limb_picker.z_axis.normalized() * (limb_picker.tail - limb_picker.head).magnitude * 1.5

                        b.roll = limb_picker.roll

                        # Set parent
                        b.parent = get_edit_bone("Picker")

                        bpy.ops.object.mode_set(mode='POSE')
                        picker_pbone = get_pose_bone(ik_limb_ctrl + "_proxy" + side)

                        picker_pbone["proxy"] = ik_limb_ctrl + side

                        if bpy.data.objects.get("cs_ctrl_ik_solid_red"):
                            if side[-2:] == ".l":
                                picker_pbone["normal_shape"] = "cs_ctrl_ik_solid_red"
                                picker_pbone["select_shape"] = "cs_ctrl_ik_solid_red_sel"

                            if side[-2:] == ".r":
                                picker_pbone["normal_shape"] = "cs_ctrl_ik_solid_blue"
                                picker_pbone["select_shape"] = "cs_ctrl_ik_solid_blue_sel"

                        # Assign color group
                        try:
                            picker_pbone.bone_group = bpy.context.active_object.pose.bone_groups["body" + side[-2:]]
                        except:
                            print('Bone group "body' + side[-2:] + ' not found')

                        # Assign custom shape (proxy picker does not update)
                        try:
                            picker_pbone.custom_shape = bpy.data.objects[picker_pbone["normal_shape"]]
                        except:
                            print("Could not set the shape of " + ik_limb_ctrl + " bone")

                        # Assign layer
                        for layer in range(0, 32):
                            if layer == set_in_layer:
                                picker_pbone.bone.layers[layer] = True
                            else:
                                picker_pbone.bone.layers[layer] = False

                        # Add arp_layer prop
                        picker_pbone["arp_layer"] = 0
                        picker_pbone.bone["arp_layer"] = 0

                        # Disable deform
                        picker_pbone.bone.use_deform = False

                    bpy.ops.object.mode_set(mode='EDIT')

                    print("Added " + ik_limb_ctrl + side)

            # Add IK thigh controller
            for side in leg_sides:
                if get_edit_bone("c_thigh_ik" + side):
                    add_ik_ctrl("c_thigh_ik", "thigh_ik_nostr", "leg_ik_nostr", "c_foot_ik", side)
            for side in arm_sides:
                if get_edit_bone("c_arm_ik" + side):
                    add_ik_ctrl("c_arm_ik", "arm_ik_nostr", "forearm_ik_nostr", "c_hand_ik", side)

            # Update bone properties
            duplicate_bones = ['thigh_ref.l', 'leg_ref.l', 'foot_ref.l', 'thigh_ref.r', 'leg_ref.r', 'foot_ref.r',
                               'shoulder_ref.r', 'arm_ref.r', 'forearm_ref.r', 'hand_ref.r', 'shoulder_ref.l', 'arm_ref.l',
                               'forearm_ref.l', 'hand_ref.l', 'head_ref.x', 'neck_ref.x', 'ear_01_ref.l', 'ear_02_ref.l',
                               'ear_01_ref.r', 'ear_02_ref.r']

            for bone in duplicate_bones:
                if get_edit_bone(bone) != None:
                    get_edit_bone(bone)['arp_duplicate'] = 1.0

            # Update bones name
            try:
                get_edit_bone('c_stretch_arm_pin_proxy.r.001').name = 'c_stretch_leg_pin_proxy.r'
                get_edit_bone('c_stretch_arm_pin_proxy.l.001').name = 'c_stretch_leg_pin_proxy.l'
            except:
                pass
            try:
                get_edit_bone('eye_ref.l').name = 'c_eye_ref.l'
                get_edit_bone('eye_ref.r').name = 'c_eye_ref.r'
                need_update = True
            except:
                pass

            for bone in bpy.context.active_object.data.edit_bones:
                if "c_head_scale_fix" in bone.name:
                    bone.name = bone.name.replace("c_", "")
                    print("c_head_scale_fix has been renamed to head_scale_fix")

                if "c_neck_thick_proxy" in bone.name:
                    bone.name = bone.name.replace("thick", "01")

            if get_pose_bone('c_eye_ref_proxy.l'):
                get_pose_bone('c_eye_ref_proxy.l')['proxy'] = 'c_eye_ref.l'
            if get_pose_bone('c_eye_ref_proxy.r'):
                get_pose_bone('c_eye_ref_proxy.r')['proxy'] = 'c_eye_ref.r'

            # Update layers
            for bone in bpy.context.active_object.data.edit_bones:
                try:
                    bone['arp_layer'] = ard.bones_arp_layer[bone.name]

                except:
                    pass

                # Controllers must not be in protected layers
                if bone.layers[0]:
                    for i in range(8, 15):
                        bone.layers[i] = False
                    for i in range(24, 30):
                        bone.layers[i] = False

            # Un-protect the layer 31 (some controllers are deformer too)
            bpy.context.active_object.data.layers_protected[31] = False

            bpy.ops.object.mode_set(mode='POSE')

            for bone in bpy.context.active_object.pose.bones:
                try:
                    bone['arp_layer'] = ard.bones_arp_layer[bone.name]
                except:
                    pass

            for side in leg_sides:
                if get_pose_bone("c_foot_ik" + side):
                    if len(get_pose_bone("c_foot_ik" + side).keys()) > 0:
                        if not "fix_roll" in get_pose_bone("c_foot_ik" + side).keys():
                            get_pose_bone('c_foot_ik' + side)['fix_roll'] = 0.0
                            get_pose_bone('c_foot_ik' + side)['fix_roll'] = 0.0
                            print('....Bone properties updated')

            bpy.ops.object.mode_set(mode='EDIT')

            # Update parent
            # Arms
            for side in arm_sides:
                shoulder_bend = get_edit_bone("c_shoulder_bend" + side)
                if shoulder_bend:
                    shoulder_bend.parent = get_edit_bone("arm_twist" + side)

                wrist_bend = get_edit_bone("c_wrist_bend" + side)
                if wrist_bend:
                    wrist_bend.parent = get_edit_bone("forearm_twist" + side)

            # Legs
            for side in leg_sides:

                foot_bank_01 = get_edit_bone('foot_bank_01_ref' + side)
                if foot_bank_01:
                    if foot_bank_01.parent == None:
                        foot_bank_01.parent = get_edit_bone('foot_ref' + side)

                thigh_bend_contact = get_edit_bone("c_thigh_bend_contact" + side)
                if thigh_bend_contact:
                    thigh_bend_contact.parent = get_edit_bone("thigh_twist" + side)

                thigh_bend_01 = get_edit_bone("c_thigh_bend_01" + side)
                if thigh_bend_01:
                    thigh_bend_01.parent = get_edit_bone("thigh_twist" + side)

                ankle_bend = get_edit_bone("c_ankle_bend" + side)
                if ankle_bend:
                    ankle_bend.parent = get_edit_bone("leg_twist" + side)

            # Lips roll
            for head_side in head_sides:
                lips_roll_bot = get_edit_bone('c_lips_roll_bot' + head_side)
                if lips_roll_bot:
                    if lips_roll_bot.parent.name == "c_skull_01" + head_side:
                        lips_roll_bot.parent = get_edit_bone("c_jawbone" + head_side)


            # Spine
            # change the root bones relationships for better skinning
            c_root_bend = get_edit_bone('c_root_bend.x')
            root = get_edit_bone('root.x')
            c_root = get_edit_bone('c_root.x')
            waist_bend = get_edit_bone('c_waist_bend.x')

            rig_add = get_rig_add(bpy.context.active_object)

            if c_root_bend and root and c_root and waist_bend:
                updated_root_bone = False

                if c_root_bend.parent == root:
                    c_root_bend.parent = c_root
                    print("Changed c_root_bend.x parent")
                    updated_root_bone = True

                if root.parent == c_root:
                    root.parent = c_root_bend
                    print("Changed root.x parent")
                    updated_root_bone = True

                for side in ['.l', '.r']:
                    bot_bend = get_edit_bone('c_bot_bend' + side)
                    if bot_bend:
                        if bot_bend.parent != c_root_bend:
                            continue

                        bot_bend.parent = root
                        print("Changed", bot_bend.name, "parent")
                        bot_bend.use_deform = True
                        updated_root_bone = True

                        # disabled additive bot deform, enable direct deform
                        if rig_add == None:
                            continue
                        rig_add_bot = rig_add.data.bones.get(bot_bend.name)
                        if rig_add_bot:
                            rig_add_bot.use_deform = False

                if waist_bend.parent == root:
                    waist_bend.parent = c_root
                    updated_root_bone = True

                tail = get_edit_bone("c_tail_00.x")
                if tail:
                    if tail.parent == root:
                        tail.parent = c_root
                        print("Changed tail parent")
                        updated_root_bone = True

                if c_root_bend.use_deform:
                    c_root_bend.use_deform = False
                    print("Disabled c_root_bend deform")
                    updated_root_bone = True

                # merge the c_root_bend.x vgroup to root.x vgroup
                def transfer_weight_t1(object=None, vertice=None, vertex_weight=None, group_name=None, dict=None,
                                    target_group=None):
                    if group_name in dict:
                        _target_group = dict[group_name]
                        # create the vgroup if necessary
                        if object.vertex_groups.get(_target_group) == None:
                            object.vertex_groups.new(name=_target_group)
                            # asssign weights
                        object.vertex_groups[_target_group].add([vertice.index], vertex_weight, 'ADD')
                        return True

                if updated_root_bone:
                    transfer_dict = {'c_root_bend.x': 'root.x'}
                    for obj in bpy.data.objects:
                        if len(obj.vertex_groups) > 0 and obj.type == "MESH":
                            transferred_weights = False
                            for vert in obj.data.vertices:
                                for grp in vert.groups:
                                    try:
                                        grp_name = obj.vertex_groups[grp.group].name
                                    except:
                                        continue
                                    weight = grp.weight
                                    transfer_result = transfer_weight_t1(object=obj, vertice=vert, vertex_weight=weight,
                                                                      group_name=grp_name, dict=transfer_dict)
                                    if transfer_result:
                                        transferred_weights = True

                            # remove the unnecessary group
                            if transferred_weights:
                                print("c_root_bend.x weights transferred to root.x")
                                obj.vertex_groups.remove(obj.vertex_groups['c_root_bend.x'])

            # create new bones
            if get_edit_bone('c_p_foot.l'):
                bpy.ops.armature.select_all(action='DESELECT')
                get_edit_bone('c_p_foot.l').select = True
                get_edit_bone('c_p_foot.r').select = True
                bpy.ops.object.mode_set(mode='POSE')
                bpy.ops.object.mode_set(mode='EDIT')  # debug selection

                duplicate(type="EDIT_BONE")

                get_edit_bone('c_p_foot.l.001').name = 'c_p_foot_fk.l'
                get_edit_bone('c_p_foot.r.001').name = 'c_p_foot_fk.r'

                get_edit_bone('c_p_foot.l').name = 'c_p_foot_ik.l'
                get_edit_bone('c_p_foot.r').name = 'c_p_foot_ik.r'
                print('....New bones created')

            # update neck twist
            for side in head_sides:
                if get_edit_bone("neck" + side):
                    if get_edit_bone('neck_twist' + side) == None:
                        print("Creating neck twist...")
                        nbone = bpy.context.active_object.data.edit_bones.new("neck_twist" + side)
                        nbone.head = get_edit_bone("neck" + side).tail
                        nbone.tail = nbone.head + (get_edit_bone("head" + side).tail - get_edit_bone("head" + side).head) * 0.5
                        nbone.roll = get_edit_bone('head' + side).roll
                        nbone.parent = get_edit_bone('neck' + side)
                        nbone.use_connect = True
                        nbone.use_deform = False

                        for idx in range(len(nbone.layers)):
                            if idx != 8:
                                nbone.layers[idx] = False

                        get_edit_bone('neck' + side).bbone_segments = 5
                        get_edit_bone('neck' + side).bbone_easein = 0.0
                        get_edit_bone('neck' + side).bbone_easeout = 0.0
                        bpy.ops.object.mode_set(mode='POSE')
                        bpy.ops.object.mode_set(mode='EDIT')
                        pose_nbone = get_pose_bone("neck_twist" + side)
                        cns = pose_nbone.constraints.new("COPY_ROTATION")
                        cns.target = bpy.context.active_object
                        cns.subtarget = 'head' + side

                        print("Created.")

            # update shoulders
            for side in arm_sides:
                c_p_shoulder = get_edit_bone('c_p_shoulder' + side)
                if c_p_shoulder:
                    if c_p_shoulder.parent != get_edit_bone('c_shoulder' + side):
                        c_p_shoulder.parent = get_edit_bone('c_shoulder' + side)
                        print("c_p_shoulder updated.")

            # update layers
            for side in sides:
                switch_bone_layer('eyelid_top' + side, 0, 8, False)
                switch_bone_layer('eyelid_bot' + side, 0, 8, False)

            # update toes roll
            for side in leg_sides:
                toes_ref = get_edit_bone('toes_ref' + side)
                foot_ref = get_edit_bone('foot_ref' + side)
                if toes_ref and foot_ref:
                    bpy.ops.armature.select_all(action='DESELECT')
                    bpy.context.active_object.data.edit_bones.active = toes_ref
                    bpy.context.active_object.data.edit_bones.active = foot_ref
                    bpy.ops.armature.calculate_roll(type='ACTIVE')
                    print('Toes roll updated,', side)

            # update spine proxy locations
            head_b = get_edit_bone("c_head_proxy.x")
            if head_b:
                if round(head_b.head[2], 2) != -6.09 and head_b.head[2] > -8:  # only for old layout position
                    print("Old picker layout detected, update spine button position...")
                    spine_dict = ard.bone_update_locations

                    for bone in spine_dict:
                        get_edit_bone(bone).head, get_edit_bone(bone).tail = spine_dict[bone]

            # change the eye target controller parent to Child Of constraint
            for head_side in head_sides:
                c_eye_target = get_edit_bone("c_eye_target" + head_side)
                if c_eye_target:
                    if c_eye_target.parent:
                        print("Replacing eye_target parent by Child Of constraints...")
                        c_eye_target.parent = None
                        bpy.ops.object.mode_set(mode='POSE')
                        eye_target_pbone = get_pose_bone("c_eye_target" + head_side)
                        cns = eye_target_pbone.constraints.new("CHILD_OF")
                        cns.target = bpy.context.active_object
                        cns.subtarget = get_first_master_controller()
                        cns.inverse_matrix = get_pose_bone(cns.subtarget).matrix.inverted()
                        print("eye_target constraint created.")

            # update the lips retain
            for head_side in head_sides:
                bpy.ops.object.mode_set(mode='EDIT')
                jaw = get_edit_bone('c_jawbone' + head_side)
                jaw_ret = get_edit_bone('jaw_ret_bone' + head_side)
                if jaw and not jaw_ret:
                    _lips_bones = ['c_lips_top' + head_side, 'c_lips_top', 'c_lips_top_01', 'c_lips_smile', 'c_lips_bot_01',
                                   'c_lips_bot', 'c_lips_bot' + head_side]

                    lips_offset_dict = {}

                    # create jaw_retain bone
                    print("creating jaw_ret_bone...")
                    jaw_ret_bone = bpy.context.active_object.data.edit_bones.new('jaw_ret_bone' + head_side)
                    jaw_ret_bone.head = jaw.head
                    jaw_ret_bone.tail = jaw.tail
                    jaw_ret_bone.tail = jaw_ret_bone.head + (jaw_ret_bone.tail - jaw_ret_bone.head) * 0.8
                    jaw_ret_bone.roll = jaw.roll
                    jaw_ret_bone.parent = jaw.parent
                    jaw_ret_bone.use_deform = False

                    # set to layer 8
                    jaw_ret_bone.layers[8] = True
                    for i, layer in enumerate(jaw_ret_bone.layers):
                        if i != 8:
                            jaw_ret_bone.layers[i] = False

                    for _bone in _lips_bones:
                        for side in sides:
                            if _bone[-2:] != '.x':
                                bone = get_edit_bone(_bone + head_side[:-2] + side)
                            else:
                                bone = get_edit_bone(_bone)

                            # create lips retain bones
                            subs = -2
                            if '_dupli_' in bone.name:
                                subs = -12

                            _ret_bone_name = bone.name[:subs] + '_retain' + bone.name[subs:]
                            _ret_bone = get_edit_bone(_ret_bone_name)
                            if _ret_bone == None:
                                _ret_bone = bpy.context.active_object.data.edit_bones.new(_ret_bone_name)
                                _ret_bone.head = bone.head.copy()
                                _ret_bone.tail = bone.tail.copy()
                                _ret_bone.tail = (_ret_bone.tail - _ret_bone.head) * 1.8 + _ret_bone.head
                                _ret_bone.roll = bone.roll
                                _ret_bone.parent = jaw_ret_bone
                                _ret_bone.use_deform = False

                                # set to layer 8
                                _ret_bone.layers[8] = True
                                for i, layer in enumerate(_ret_bone.layers):
                                    if i != 8:
                                        _ret_bone.layers[i] = False


                                        # create offset bones
                            off_bone_name = bone.name[:subs] + '_offset' + bone.name[subs:]
                            off_bone = get_edit_bone(off_bone_name)
                            if off_bone == None:
                                offset_bone = bpy.context.active_object.data.edit_bones.new(off_bone_name)
                                offset_bone.head, offset_bone.tail, offset_bone.roll, offset_bone.parent = [bone.head.copy(),
                                                                                                            bone.tail.copy(),
                                                                                                            bone.roll,
                                                                                                            bone.parent]
                                offset_bone.tail = (offset_bone.tail - offset_bone.head) * 1.5 + offset_bone.head
                                offset_bone.use_deform = False
                                bone.parent = offset_bone
                                lips_offset_dict[offset_bone.name] = None

                                # set to layer 8
                                offset_bone.layers[8] = True
                                for i, layer in enumerate(offset_bone.layers):
                                    if i != 8:
                                        offset_bone.layers[i] = False

                                        # create jaw_ret_bone constraint
                    bpy.ops.object.mode_set(mode='POSE')

                    jaw_ret_pbone = get_pose_bone('jaw_ret_bone' + head_side)
                    jaw_pbone = get_pose_bone('c_jawbone' + head_side)

                    cns = jaw_ret_pbone.constraints.new('COPY_TRANSFORMS')
                    cns.target = bpy.context.active_object
                    cns.subtarget = 'c_jawbone' + head_side
                    cns.influence = 0.5

                    # create lips offset constraints
                    for lip_offset in lips_offset_dict:
                        lip_pbone = get_pose_bone(lip_offset)
                        cns_offset = lip_pbone.constraints.new('COPY_TRANSFORMS')
                        cns_offset.target = bpy.context.active_object
                        cns_offset.subtarget = lip_offset.replace('_offset', '_retain')
                        cns_offset.influence = 1.0

                        # create drivers
                        new_driver = cns_offset.driver_add('influence')
                        new_driver.driver.expression = 'var'
                        var = new_driver.driver.variables.new()
                        var.name = 'var'
                        var.type = 'SINGLE_PROP'
                        var.targets[0].id_type = 'OBJECT'
                        var.targets[0].id = bpy.context.active_object

                        # make sure the properties exists
                        if not 'lips_retain' in jaw_pbone.keys():
                            create_custom_prop(node=jaw_pbone, prop_name='lips_retain', prop_val=0.0, prop_min=0.0, prop_max=1.0, prop_description="Maintain the lips sealed when opening the jaw")
                        #   create the driver data path
                        var.targets[0].data_path = 'pose.bones["c_jawbone' + head_side + '"]["lips_retain"]'

                    print('....Lips retain updated')


                    # update the lips stretch
                    # make sure the properties exists
                jaw_pbone = get_pose_bone('c_jawbone' + head_side)
                jaw_ret_pbone = get_pose_bone('jaw_ret_bone' + head_side)

                if jaw_pbone:
                    if not 'lips_stretch' in jaw_pbone.keys():
                        create_custom_prop(node=jaw_pbone, prop_name='lips_stretch', prop_val=1.0, prop_min=0.0, prop_max=5.0, prop_description="Stretch and squash the lips when retain is enabled")                      

                        # create driver
                        # x scale
                        jaw_ret_driver = jaw_ret_pbone.driver_add('scale', 0)
                        jaw_ret_driver.driver.expression = "max(0.05, 1 - jaw_rot * stretch_value)"

                        var_jaw_rot = jaw_ret_driver.driver.variables.new()
                        var_jaw_rot.name = 'jaw_rot'
                        var_jaw_rot.type = 'SINGLE_PROP'
                        var_jaw_rot.targets[0].id_type = 'OBJECT'
                        var_jaw_rot.targets[0].id = bpy.context.active_object
                        var_jaw_rot.targets[0].data_path = 'pose.bones["c_jawbone' + head_side + '"].rotation_euler[0]'

                        var_stretch_value = jaw_ret_driver.driver.variables.new()
                        var_stretch_value.name = 'stretch_value'
                        var_stretch_value.type = 'SINGLE_PROP'
                        var_stretch_value.targets[0].id_type = 'OBJECT'
                        var_stretch_value.targets[0].id = bpy.context.active_object
                        var_stretch_value.targets[0].data_path = 'pose.bones["c_jawbone' + head_side + '"]["lips_stretch"]'

                        print("....Lips stretch updated")

                    # Update Jaw
                    bpy.ops.object.mode_set(mode='EDIT')
                    c_jaw = get_edit_bone('c_jawbone' + head_side)
                    if c_jaw:
                        if get_edit_bone('jawbone' + head_side) == None:
                            print('jawbone' + head_side + ' is missing, updating...')

                            jaw = bpy.context.active_object.data.edit_bones.new('jawbone' + head_side)
                            print('...created jawbone' + head_side)

                            # align
                            copy_bone_transforms(c_jaw, jaw)

                            # change parents
                            for b in bpy.context.active_object.data.edit_bones:
                                if b.parent == c_jaw:
                                    b.parent = jaw
                            print('... parents changed')
                            jaw.parent = c_jaw

                            # Set color group
                            bpy.ops.object.mode_set(mode='POSE')
                            jaw_pbone = get_pose_bone('jawbone' + head_side)
                            c_jaw_pbone = get_pose_bone('c_jawbone' + head_side)
                            jaw_pbone.bone_group = c_jaw_pbone.bone_group

                            # Deform property
                            c_jaw_pbone.bone.use_deform = False

                            # set layers
                            jaw_pbone.bone.layers[8] = True
                            for i, layer in enumerate(jaw_pbone.bone.layers):
                                if i != 8:
                                    jaw_pbone.bone.layers[i] = False

                                    # change c_jawbone vgroups to jawbone
                            for obj in bpy.data.objects:
                                if obj.type == 'MESH':
                                    if len(obj.vertex_groups) > 0:
                                        if obj.vertex_groups.get('c_jawbone' + head_side):
                                            obj.vertex_groups['c_jawbone' + head_side].name = 'jawbone' + head_side

                            print('Jaw Updated.')
                            bpy.ops.object.mode_set(mode='EDIT')

                        # Full update of the jaw controller (based on translation instead of rotation, more user friendly)

                        bpy.ops.object.mode_set(mode='POSE')

                        if len(get_pose_bone('jawbone' + head_side).constraints) == 0:
                            print('\nFull update of jaw controller...')
                            bpy.ops.object.mode_set(mode='EDIT')
                            c_jaw = get_edit_bone('c_jawbone' + head_side)
                            jaw = get_edit_bone('jawbone' + head_side)

                            # change parent
                            jaw.parent = c_jaw.parent

                            # align positions
                            c_jaw.head = jaw.head + (jaw.tail - jaw.head) * 0.5
                            c_jaw.tail = c_jaw.head + (jaw.tail - jaw.head) * 0.5
                            c_jaw.roll = jaw.roll

                            # setup constraints
                            bpy.ops.object.mode_set(mode='POSE')
                            jaw_pbone = get_pose_bone('jawbone' + head_side)
                            c_jaw_pbone = get_pose_bone('c_jawbone' + head_side)

                            cns2 = jaw_pbone.constraints.new('COPY_ROTATION')
                            cns2.target = bpy.context.active_object
                            cns2.subtarget = 'c_jawbone' + head_side

                            cns = jaw_pbone.constraints.new('DAMPED_TRACK')
                            cns.target = bpy.context.active_object
                            cns.subtarget = 'c_jawbone' + head_side

                            # change constraints links
                            for pb in bpy.context.active_object.pose.bones:
                                if len(pb.constraints) > 0 and pb != jaw_pbone:
                                    for cns in pb.constraints:
                                        try:
                                            if cns.target:
                                                if cns.subtarget == 'c_jawbone' + head_side:
                                                    cns.subtarget = 'jawbone' + head_side
                                        # no target property linked to this constraint
                                        except:
                                            pass

                            print('...constraints set')

                            # set custom shapes
                            jaw_pbone.custom_shape = None
                            c_jaw_pbone.bone.show_wire = True

                            if bpy.data.objects.get('rig_ui'):
                                if bpy.data.objects.get('cs_jaw_square') == None:
                                    obj_to_append = ['cs_jaw_square']
                                    append_from_arp(nodes=obj_to_append, type='object')
                                    print('...appended', obj_to_append)

                            c_jaw_pbone.custom_shape = bpy.data.objects['cs_jaw_square']

                            # Set rotation mode
                            # Set transforms loks
                            for i in range(0, 3):
                                c_jaw_pbone.lock_location[i] = False

                            c_jaw_pbone.lock_location[1] = True

                            # update lips retain drivers
                            for driver in bpy.context.active_object.animation_data.drivers:
                                dp_prop = driver.data_path.split('.')[len(driver.data_path.split('.')) - 1]
                                if 'jaw_ret_bone' in driver.data_path and dp_prop == 'scale':
                                    jaw_ret_name = driver.data_path.split('"')[1]
                                    jaw_ret_length = str(round(get_data_bone(jaw_ret_name).length, 4) * 140)
                                    dr = driver.driver
                                    dr.expression = 'max(0.05, 1 - jaw_rot * ' + jaw_ret_length + ' * stretch_value)'
                                    base_var = dr.variables['jaw_rot']
                                    base_var.targets[0].data_path = base_var.targets[0].data_path.replace("rotation_euler[0]",
                                                                                                          "location[2]")

                            print('Jaw fully updated.')


                    else:
                        print('c_jawbone.x not found')

            bpy.ops.object.mode_set(mode='POSE')

            # Update the base fingers picker shapes
            fingers_base = ["c_pinky1", "c_ring1", "c_middle1", "c_index1", "c_thumb1"]

            if bpy.data.objects.get("cs_solid_circle_02_red"):

                for bone_n in fingers_base:
                    for side in arm_sides:

                        mat_color = "_red"
                        if side == ".r":
                            mat_color = "_blue"

                        if get_pose_bone(bone_n + "_base_proxy" + side):
                            pbone_proxy = get_pose_bone(bone_n + "_base_proxy" + side)

                            if pbone_proxy["normal_shape"] != "cs_solid_circle_02" + mat_color:
                                pbone_proxy["normal_shape"] = "cs_solid_circle_02" + mat_color
                                pbone_proxy["select_shape"] = "cs_solid_circle_02" + mat_color + "_sel"
                                pbone_proxy.custom_shape = bpy.data.objects["cs_solid_circle_02" + mat_color]
                                print("Updated picker shape of", bone_n + "_base_proxy" + side)

            # Update the base fingers shapes
            shapes_to_append = []
            if bpy.data.objects.get("cs_base_finger_end") == None:
                shapes_to_append.append("cs_base_finger_end")
                print('Appended "cs_base_finger_end"')
            if bpy.data.objects.get("cs_base_finger") == None:
                shapes_to_append.append("cs_base_finger")
                print('Appended "cs_base_finger"')

            if len(shapes_to_append) > 0:
                append_from_arp(nodes=shapes_to_append, type="object")

                for side in arm_sides:
                    for f_name in fingers_base:
                        pb = get_pose_bone(f_name.replace("1", "1_base") + side)
                        if pb and not "cs_user" in pb.custom_shape.name:
                            if not "pinky" in f_name:
                                pb.custom_shape = bpy.data.objects["cs_base_finger"]
                                set_custom_shape_scale(pb, 0.3)
                                print("Updated", pb.name, "custom shape")

                            else:  # pinky
                                pb.custom_shape = bpy.data.objects["cs_base_finger_end"]
                                set_custom_shape_scale(pb, 0.3)
                                pb.lock_location[0] = pb.lock_location[1] = pb.lock_location[2] = True
                                print("Updated", pb.name, "custom shape")

            # Update secondary arm bones shapes
            for side in arm_sides:
                for add_bone in ard.arm_bones_rig_add:
                    if not get_pose_bone(add_bone + side):
                        continue
                    if not get_pose_bone(add_bone + side).custom_shape:
                        continue
                    if get_pose_bone(add_bone + side).custom_shape.name == "cs_circle_02":
                        get_pose_bone(add_bone + side).custom_shape = bpy.data.objects["cs_torus_02"]
                        print("Updated " + add_bone + side + ' shape.')

            # Update constraints
            # Arms
            for side in arm_sides:
                # shoulders
                cp_shoulder = get_pose_bone('c_p_shoulder' + side)
                if cp_shoulder:
                    arm_stretch = get_pose_bone('arm_stretch' + side)
                    cns = None
                    if len(cp_shoulder.constraints) > 0:
                        cns = cp_shoulder.constraints[0]
                    if cns:
                        enable_constraint(get_pose_bone('c_p_shoulder' + side).constraints[0], False)                      
                    copy_loc_cns = arm_stretch.constraints.get('Copy Location')

                    if not copy_loc_cns:  # new twist bones have no more this constraints
                        continue

                    copy_loc_cns.subtarget = 'arm_twist' + side
                    if bpy.context.active_object.arp_secondary_type != "BENDY_BONES":
                        copy_loc_cns.head_tail = 1.0
                    if bpy.context.active_object.arp_secondary_type == "BENDY_BONES":
                        copy_loc_cns.head_tail = 0.0

            for side in arm_sides:
                # forearm twist
                forearm_twist = get_pose_bone("forearm_twist" + side)
                if forearm_twist:
                    damped_track_cns = forearm_twist.constraints.get("Damped Track")
                    if damped_track_cns == None:
                        cns = forearm_twist.constraints.new("DAMPED_TRACK")
                        cns.name = "Damped Track"
                        cns.target = bpy.context.active_object
                        cns.subtarget = "hand" + side
                        # move up in stack
                        bpy.context.active_object.data.bones.active = forearm_twist.bone
                        my_context = bpy.context.copy()
                        my_context["constraint"] = cns
                        bpy.ops.constraint.move_up(my_context, constraint=cns.name, owner='BONE')
                        print("Added Damped Track to forearm_twist" + side)

            # Legs
            for side in leg_sides:
                if get_pose_bone('thigh_stretch' + side):
                    thigh_stretch = get_pose_bone('thigh_stretch' + side)
                    copy_loc_cns = thigh_stretch.constraints.get('Copy Location')

                    if copy_loc_cns == None:  # new twist bones have no more this constraint
                        continue

                    copy_loc_cns.subtarget = 'thigh_twist' + side
                    if bpy.context.active_object.arp_secondary_type != "BENDY_BONES":
                        copy_loc_cns.head_tail = 1.0
                    if bpy.context.active_object.arp_secondary_type == "BENDY_BONES":
                        copy_loc_cns.head_tail = 0.0

            # Update fcurve datapath
            def replace_fcurve_dp(action, replace_this, by_this):
                for fcurve in action.fcurves:
                    if replace_this in fcurve.data_path:
                        fcurve.data_path = fcurve.data_path.replace(replace_this, by_this)

            def replace_fcurve_grp(action, replace_this, by_this):
                for group in action.groups:
                    if replace_this in group.name:
                        group.name = group.name.replace(replace_this, by_this)

            if len(bpy.context.blend_data.actions) > 0:
                for action in bpy.context.blend_data.actions:
                    replace_fcurve_dp(action, 'c_ikfk_arm', 'c_hand_ik')
                    replace_fcurve_dp(action, 'c_ikfk_leg', 'c_foot_ik')
                    replace_fcurve_grp(action, 'c_ikfk_arm', 'c_hand_ik')
                    replace_fcurve_grp(action, 'c_ikfk_leg', 'c_foot_ik')

                # remove invalid fcurve // BUGGY, must be run several time :-/
                invalid_fcurves = []
                for fcurve in action.fcurves:
                    if not fcurve.is_valid:
                        invalid_fcurves.append(fcurve)

                for fc in invalid_fcurves:
                    action.fcurves.remove(fc)

            # Replace depracted groups names
            depracated_groups_list = ["c_pinky1", "c_ring1", "c_middle1", "c_index1", "c_thumb1"]

            found_new_finger_bone = False

            for bone in bpy.context.active_object.data.bones:
                if bone.name == "index1.l":
                    found_new_finger_bone = True
                    break

            if found_new_finger_bone:
                for obj in bpy.data.objects:
                    if len(obj.vertex_groups) > 0:
                        for vgroup in obj.vertex_groups:
                            for name in depracated_groups_list:
                                if name in vgroup.name and not "base" in vgroup.name:
                                    vgroup.name = vgroup.name[2:]
                                    print("Replaced vertex group:", vgroup.name)

            secondary_eyelids = ["c_eyelid_top_01", "c_eyelid_top_02", "c_eyelid_top_03", "c_eyelid_corner_01",
                                 "c_eyelid_corner_02", "c_eyelid_bot_01", "c_eyelid_bot_02", "c_eyelid_bot_03"]

            rig_name = bpy.context.active_object.name


            bpy.ops.object.mode_set(mode='EDIT')

            # Find facial duplis
            facial_duplis = []

            for bone in bpy.context.active_object.data.edit_bones:
                if "eyelid" in bone.name:
                    _side = get_bone_side(bone.name)

                    if not _side in facial_duplis:
                        facial_duplis.append(_side)

            bpy.ops.object.mode_set(mode='POSE')

            for dupli in facial_duplis:

                # Replace the eyes Track_To cns by Damped_Track to avoid rotation issues
                eye = get_pose_bone('c_eye' + dupli)
                if eye:
                    track_cns = None
                    found = False
                    if len(eye.constraints) > 0:
                        for cns in eye.constraints:
                            if cns.type == 'DAMPED_TRACK':
                                found = True
                            if cns.type == 'TRACK_TO':
                                track_cns = cns

                    if not found and track_cns:
                        print('Adding the eyelid Damped Track constraint...')
                        # create the damped_track constraint
                        cns_damp = eye.constraints.new('DAMPED_TRACK')
                        cns_damp.target = track_cns.target
                        cns_damp.subtarget = track_cns.subtarget

                        # create drivers
                        new_driver = cns_damp.driver_add("influence")
                        new_driver.driver.expression = "var"
                        var = new_driver.driver.variables.new()
                        var.name = "var"
                        var.type = "SINGLE_PROP"
                        var.targets[0].id_type = "OBJECT"
                        var.targets[0].id = bpy.context.active_object
                        var.targets[0].data_path = 'pose.bones["c_eye_target.x"]["eye_target"]'

                        # remove the track_to constraint
                        eye.constraints.remove(track_cns)

                # add a Limit Rotation constraint to avoid rotations issues with the auto-eyelids
                eyelid_base = get_pose_bone('c_eyelid_base' + dupli)
                if eyelid_base:
                    found = False
                    if len(eyelid_base.constraints) > 0:
                        for cns in eyelid_base.constraints:
                            if cns.type == 'LIMIT_ROTATION':
                                found = True

                        if not found:
                            print('Adding the Limit Rotation constraint...')
                            limit_cns = eyelid_base.constraints.new('LIMIT_ROTATION')
                            limit_cns.use_limit_x = limit_cns.use_limit_y = limit_cns.use_limit_z = True
                            limit_cns.min_x = radians(-30)
                            limit_cns.max_x = radians(10)
                            limit_cns.min_y = limit_cns.max_y = 0.0
                            limit_cns.min_z = radians(-20)
                            limit_cns.max_z = radians(20)
                            limit_cns.owner_space = 'LOCAL'

            bpy.ops.object.mode_set(mode='EDIT')

            # Make sure the secondary eyelids are parented to eyelid_top and eyelid_bot
            for b in secondary_eyelids:
                for dupli in facial_duplis:
                    bo = get_edit_bone(b + dupli)
                    if bo:
                        if not "corner" in b:
                            id = b.split('_')[2]
                            parent_bone = get_edit_bone('eyelid_' + id + dupli)
                            if parent_bone:
                                bo.parent = parent_bone

            # Update eyelids controllers, rotation based to translation based
            bpy.ops.object.mode_set(mode='POSE')
            update_eyelids = True
            if get_pose_bone("eyelid_top.l"):
                if len(get_pose_bone("eyelid_top.l").constraints) == 0:
                    update_eyelids = True
                else:
                    update_eyelids = False

            bpy.ops.object.mode_set(mode='EDIT')

            if update_eyelids:
                xmirror_state = bpy.context.active_object.data.use_mirror_x
                bpy.context.active_object.data.use_mirror_x = False

                print("\nUpdating eyelids controllers...")
                for side in facial_duplis:
                    bpy.ops.object.mode_set(mode='POSE')

                    if get_pose_bone("c_eye_offset" + side):
                        for id in ["_top", "_bot"]:
                            if get_pose_bone("eyelid" + id + side):
                                # if it has no constraint, we assume the bone is from an older version and delete it
                                if len(get_pose_bone("eyelid" + id + side).constraints) == 0:
                                    bpy.ops.object.mode_set(mode='EDIT')
                                    delete_edit_bone(get_edit_bone("eyelid" + id + side))
                                    print("...Deleted old eyelid" + id + side)
                                    bpy.ops.object.mode_set(mode='POSE')

                            if get_pose_bone("eyelid" + id + side) == None:
                                bpy.ops.object.mode_set(mode='EDIT')

                                # Rename the current c_eyelid to eyelid
                                get_edit_bone("c_eyelid" + id + side).name = "eyelid" + id + side
                                eyel = get_edit_bone("eyelid" + id + side)
                                eye_offset = get_edit_bone("c_eye_offset" + side)
                                print("...Renamed c_eyelid" + id + side + " to eyelid" + id + side)

                                # Create the c_eyelid bone
                                c_eyel = bpy.context.active_object.data.edit_bones.new("c_eyelid" + id + side)
                                c_eyel.parent = eye_offset
                                c_eyel.head = eyel.tail + (eyel.tail - eyel.head) * 1.5
                                c_eyel.tail = c_eyel.head + ((
                                                                 eyel.tail - eyel.head) * 0.5)  # .magnitude * eye_offset.y_axis.normalized())
                                c_eyel.roll = eyel.roll

                                # Copy properties from the previous bone
                                for key in eyel.keys():
                                    c_eyel[key] = eyel[key]

                                print("... Created new c_eyelid" + id + side)

                                # set layers
                                c_eyel.layers[0] = True
                                for i, layer in enumerate(c_eyel.layers):
                                    if i != 0:
                                        c_eyel.layers[i] = False

                                eyel.layers[8] = True
                                eyel.layers[0] = False

                                # Deform property
                                c_eyel.use_deform = False

                                # Setup constraints
                                bpy.ops.object.mode_set(mode='POSE')

                                # By transform constraint
                                eyelid_pbone = get_pose_bone("eyelid" + id + side)
                                cns = eyelid_pbone.constraints.new("TRANSFORM")
                                cns.target = bpy.context.active_object
                                cns.subtarget = "c_eyelid" + id + side
                                cns.use_motion_extrapolate = True
                                cns.from_min_z = 0.0
                                cns.from_max_z = 1.5
                                cns.map_to_x_from = "Z"
                                cns.map_to_z_from = "X"
                                cns.map_to = "ROTATION"
                                cns.to_max_x_rot = 1.4 / eyelid_pbone.length
                                cns.target_space = cns.owner_space = "LOCAL"

                                # Other rotations axes get constrained
                                cns1 = get_pose_bone("eyelid" + id + side).constraints.new("COPY_ROTATION")
                                cns1.target = bpy.context.active_object
                                cns1.subtarget = "c_eyelid" + id + side
                                cns1.use_x = False
                                cns1.target_space = cns1.owner_space = "LOCAL"

                                # set color group
                                c_eyel_pbone = get_pose_bone("c_eyelid" + id + side)
                                c_eyel_pbone.bone_group = get_pose_bone("eyelid" + id + side).bone_group

                                # transforms locks
                                c_eyel_pbone.lock_location[0] = c_eyel_pbone.lock_location[1] = True
                                c_eyel_pbone.lock_rotation[0] = True

                                # Rotation mode
                                c_eyel_pbone.rotation_mode = "XYZ"

                                # Set custom shape
                                if bpy.data.objects.get("cs_eyelid2") == None:
                                    append_from_arp(nodes=["cs_eyelid2"], type="object")

                                c_eyel_pbone.custom_shape = bpy.data.objects["cs_eyelid2"]
                                get_pose_bone("eyelid" + id + side).custom_shape = None
                                c_eyel_pbone.bone.show_wire = True

                bpy.context.active_object.data.use_mirror_x = xmirror_state
                print("Eyelids updated.")

            # Make sure to remove any obsolete eyelid_top/eyelid_bot vertex groups from meshes
            for obj in bpy.data.objects:
                if obj.type != 'MESH':
                    continue
                if len(obj.vertex_groups) == 0:
                    continue
                for head_side in facial_duplis:
                    vgroup_to_del = ['eyelid_top' + head_side, 'eyelid_bot' + head_side]
                    for vg in vgroup_to_del:
                        vgroup = obj.vertex_groups.get(vg)
                        if vgroup:
                            obj.vertex_groups.remove(vgroup)
                            print("Deleted " + vg + " vertex group on object: " + obj.name)

            if rig_add:
                # Enable secondary eyelids bones deform and remove them from rig_add
                if rig_add.data.bones.get("c_eyelid_top_01.l"):
                    print("Removing secondary eyelids from additive bones...")

                    for b in secondary_eyelids:
                        for side in sides:
                            if get_data_bone(b + side):
                                get_data_bone(b + side).use_deform = True

                    edit_rig(rig_add)

                    for b in secondary_eyelids:
                        for side in sides:
                            if get_edit_bone(b + side):
                                delete_edit_bone(get_edit_bone(b + side))

                    bpy.ops.object.mode_set(mode='OBJECT')
                    rig_add.select_set(state=False)
                    rig_add.hide_select = True
                    hide_object(rig_add)
                    edit_rig(bpy.data.objects[rig_name])

                # remove the eye_offset bone from the rig_add
                for side in [".l", ".r"]:
                    eye_off = rig_add.data.bones.get("c_eye_offset" + side)
                    if eye_off:
                        edit_rig(rig_add)
                        delete_edit_bone(get_edit_bone("c_eye_offset" + side))

                bpy.ops.object.mode_set(mode='OBJECT')
                rig_add.select_set(state=False)
                rig_add.hide_select = True
                hide_object(rig_add)
                edit_rig(bpy.data.objects[rig_name])

            # Correct the head lock property
            bpy.ops.object.mode_set(mode='POSE')
            scale_fix_bone = None

            # retro-compatibility
            if bpy.context.active_object.data.bones.get("c_head_scale_fix.x"):
                scale_fix_bone = bpy.context.active_object.data.bones["c_head_scale_fix.x"]
            elif bpy.context.active_object.data.bones.get("head_scale_fix.x"):
                scale_fix_bone = bpy.context.active_object.data.bones["head_scale_fix.x"]

                # add the new ChildOf constraint if it's not there
            found_cns = False
            if scale_fix_bone:
                scale_fix_pbone = get_pose_bone(scale_fix_bone.name)

                for cns in scale_fix_pbone.constraints:
                    if cns.name == "Child Of_traj":
                        found_cns = True

                if not found_cns:
                    print("Head lock ChildOf constraint not found, updating...")
                    scale_fix_bone.use_inherit_scale = False
                    scale_fix_bone.use_inherit_rotation = False

                    new_cns = scale_fix_pbone.constraints.new("CHILD_OF")
                    new_cns.name = "Child Of_traj"
                    new_cns.target = bpy.context.active_object
                    new_cns.subtarget = get_first_master_controller()
                    bpy.context.active_object.data.bones.active = scale_fix_pbone.bone
                    my_context = bpy.context.copy()
                    my_context["constraint"] = new_cns
                    bpy.ops.constraint.move_up(my_context, constraint=new_cns.name, owner='BONE')
                    new_cns.inverse_matrix = get_pose_bone(new_cns.subtarget).matrix.inverted()

                    dr = bpy.context.active_object.driver_add('pose.bones["' + scale_fix_pbone.name + '"].constraints["Child Of_traj"].influence', -1)
                    dr.driver.expression = "1-var"
                    if len(dr.driver.variables) == 0:
                        base_var = dr.driver.variables.new()
                    else:
                        base_var = dr.driver.variables[0]
                    base_var.type = 'SINGLE_PROP'
                    base_var.name = 'var'
                    base_var.targets[0].id = bpy.context.active_object
                    base_var.targets[0].data_path = 'pose.bones["c_head.x"].["head_free"]'

                    print("Head lock constraint updated.")

            # Add the latest version update tag
            self.breaking = False
            
            # enable the proxy picker
            try:
                bpy.context.scene.Proxy_Picker.active = True
            except:
                pass

            # restore saved mode
            restore_current_mode(current_mode)

            # update the picker "reset all" script
            addon_directory = os.path.dirname(os.path.abspath(__file__))
            filepath = addon_directory + "/reset_all_controllers.py"
            update_file = open(filepath, 'rU')
            file_lines = [i for i in update_file.readlines()]
            update_file.close()

            for text_file in bpy.data.texts:
                if text_file.name.startswith("reset_2") and text_file.name.endswith(".py"):
                    print("updating reset all script", text_file.name)
                    text_file.clear()
                    for i in file_lines:
                        text_file.write(i)


        if update_36118:
            print("Updating 3.61.18...")

            # rig_type to arp_rig_type update
            if 'rig_type' in sel_armature.keys():
                sel_armature['arp_rig_type'] = sel_armature['rig_type']
                up_feature = "Updated 'rig_type' to 'arp_rig_type' property"
                self.updated_features.append(up_feature)
                del sel_armature['rig_type']
                
            # update tag
            sel_armature.data["arp_updated"] = '3.61.18'


        if update_30:            
            convert_drivers_cs_to_xyz(sel_armature)           
            
            up_feature = 'Updated drivers to Blender 3.0 requirements'
            self.updated_features.append(up_feature)
            
        
        self.required = False
        
    # restore layers
    restore_armature_layers(layers_select)
    
    # restore X-mirror
    sel_armature.data.use_mirror_x = xmirror_state
    
    print('\nFinished armature update')


def _disable_limb(self, context):
    
    rig = bpy.context.active_object
    scn = bpy.context.scene
    
    # display all layers
    for i in range(0, 32):
        rig.data.layers[i] = True

    # disable the proxy picker to avoid bugs
    try:
        scn.Proxy_Picker.active = False
    except:
        pass

    # Turn off mirror edit
    mirror_edit = rig.data.use_mirror_x
    rig.data.use_mirror_x = False

    sel_bone = get_selected_edit_bones()[0]
    sel_bone_name = sel_bone.name
    side = get_bone_side(sel_bone_name)
    drivers_data = rig.animation_data.drivers

    rig_add = get_rig_add(rig)

    rig_type = ''
    if rig.data.keys():
        if 'arp_rig_type' in rig.data.keys():
            rig_type = rig.data['arp_rig_type']

    def disable_display_bones(b_name):
        try:
            b = get_edit_bone(b_name)
            b.layers[22] = True
            b.layers[16] = False
            b.layers[0] = False
            b.layers[1] = False
        except:
            print(b_name, 'is invalid')

    
    limb_type = None    
    is_facial_bone = False
    
    for basename in ard.facial_ref:
        bname = basename + side
        if ".x" in basename:
            bname = basename[:-2] + side 
            
        if sel_bone_name == bname:
            is_facial_bone = True
            break
    
    if sel_bone_name.split('_')[0] == 'spline' or "arp_spline" in sel_bone.keys():
        limb_type = 'spline_ik'
        
    elif sel_bone_name.split('_')[0] == 'bbones' or "arp_bbones" in sel_bone.keys():
        limb_type = 'bbones'
        
    elif 'arm_' in sel_bone_name or 'shoulder' in sel_bone_name or 'hand' in sel_bone_name:
        limb_type = 'arm'
        
    elif 'thigh' in sel_bone_name or 'leg' in sel_bone_name or 'foot' in sel_bone_name:
        limb_type = 'leg'
        
    elif is_facial_bone:
        limb_type = 'facial'
    
    elif sel_bone_name.split('_')[0] == 'head':
        limb_type = 'head'
        
    elif sel_bone_name.split('_')[0] == 'neck':
        limb_type = 'neck'
    
    elif sel_bone_name.split('_')[0] == 'root' or sel_bone_name.split('_')[0] == 'spine':
        limb_type = 'spine'
        
    elif sel_bone_name.split('_')[0] == 'tail':
        limb_type = 'tail'
        
    elif sel_bone_name.split('_')[0] == 'breast':
        limb_type = 'breast'
        
    elif sel_bone_name.split('_')[0] == 'ear':
        limb_type = 'ear'
        
    elif sel_bone_name.split('_')[0] == 'bot' and sel_bone_name.split('_')[1] == 'bend':
        limb_type = 'bot'    
        

    def disable_finger(finger, side, bone_name):
        to_del = []
        for bone in bpy.context.active_object.data.edit_bones:
            if (side in bone.name) and (finger in bone.name):
                to_del.append(bone)

        for b in to_del:
            delete_edit_bone(b)

    def disable_head(context, side):

        disable_facial(side)

        # Bones
        for bone in ard.head_bones:
            bname = bone[:-2] + side
            cbone = get_edit_bone(bname)
            if cbone:
                delete_edit_bone(cbone)

        if "bone" in locals():
            del bone

        # Proxy
        for bone in ard.head_control:
            bname = bone[:-2] + '_proxy' + side
            proxy_bone = get_edit_bone(bname)
            if proxy_bone:
                delete_edit_bone(proxy_bone)

        if "bone" in locals():
            del bone

        # Ref bones
        for bone in ['head_ref.x']:
            bname = bone[:-2] + side
            ref_bone = get_edit_bone(bname)
            if ref_bone:
                delete_edit_bone(ref_bone)

        if "bone" in locals():
            del bone
            
        # unplug the head subtarget constraint, generates error in the console otherwise
        bpy.ops.object.mode_set(mode='POSE')
        
        for pb in rig.pose.bones:
            if len(pb.constraints):
                for cns in pb.constraints:
                    if 'subtarget' in dir(cns):
                        if cns.subtarget == 'head'+side:
                            cns.subtarget = ''
        
        bpy.ops.object.mode_set(mode='EDIT')

    def disable_facial(side):
        facial_bones = []
        # get facial bones
        for bname in ard.facial_bones:
            for sided in ['.l', '.r']:
                if bname[-2:] != ".x":
                    bone_name = bname + side[:-2] + sided
                if bname[-2:] == ".x":
                    bone_name = bname[:-2] + side[:-2] + ".x"
                facial_bones.append(bone_name)
        #   multi lips     
        for bname in ard.get_variable_lips(side, type='ALL'):            
            facial_bones.append(bname)
        
        #   multi eyelids
        for bname in ard.get_variable_eyelids(side, type='ALL'):            
            facial_bones.append(bname) 
        
        # remove
        for bone_name in facial_bones:
            cbone = get_edit_bone(bone_name)
            if cbone:
                delete_edit_bone(cbone)

        # Ref
        for bone in ard.facial_ref:
            for sided in ['.l', '.r']:
                bone_name = bone + side[:-2] + sided
                if bone[-2:] == ".x":
                    bone_name = bone[:-2] + side[:-2] + ".x"

                ref_bone = get_edit_bone(bone_name)
                if ref_bone:
                    delete_edit_bone(ref_bone)

        # Proxy
        for bone in ard.facial_control:
            for sided in ['.l', '.r']:
                bone_name = bone + '_proxy' + side[:-2] + sided
                if bone[-2:] == ".x":
                    bone_name = bone[:-2] + '_proxy' + side[:-2] + ".x"

                proxy_bone = get_edit_bone(bone_name)
                if proxy_bone:
                    delete_edit_bone(proxy_bone)

        if "bone" in locals():
            del bone

    def disable_neck(context, side):
        # Bones
        for bone in ard.neck_bones:
            b = get_edit_bone(bone[:-2] + side)
            if b:
                delete_edit_bone(b)

        if "bone" in locals():
            del bone

        # Proxy
        for bone in ard.neck_control:
            proxy_bone = get_edit_bone(bone[:-2] + '_proxy' + side)
            if proxy_bone:
                delete_edit_bone(proxy_bone)

        if "bone" in locals():
            del bone

        # Ref bones
        for bone in ['neck_ref.x']:
            ref_bone = get_edit_bone(bone[:-2] + side)
            if ref_bone:
                delete_edit_bone(ref_bone)

        if "bone" in locals():
            del bone

    def disable_spline_ik(context, side):
        # clear constraints first
        bpy.ops.object.mode_set(mode='POSE')
        
        sel_pbone = bpy.context.active_pose_bone

        spline_name = "spline"
        if sel_pbone.bone.keys():
            if "arp_spline" in sel_pbone.bone.keys():
                spline_name = sel_pbone.bone["arp_spline"]

        stretch_bone_name = spline_name + "_stretch" + side
        spline_ik_clear_constraints(stretch_bone_name, side)

        bpy.ops.object.mode_set(mode='EDIT')
        # Bones
        #   ref
        first_ref_bone = get_edit_bone(spline_name + "_01_ref" + side)
        amount = first_ref_bone["spline_count"]
        for i in range(1, amount+2):
            id = '%02d' % i
            ref_name = spline_name + "_" + id + "_ref" + side
            ref = get_edit_bone(ref_name)
            if ref:
                delete_edit_bone(ref)

            # inter
            inter_name = "c_"+spline_name+"_inter_"+id+side
            inter = get_edit_bone(inter_name)
            if inter:
                delete_edit_bone(inter)

            #masters
            master_name = "c_"+spline_name+"_master_"+id+side
            master = get_edit_bone(master_name)
            if master:
                delete_edit_bone(master)

        #   spline ik
        for i in range(1, amount + 1):
            id = '%02d' % (i)
            splineik_name = spline_name + "_" + id + side
            splineik = get_edit_bone(splineik_name)
            if splineik:
                delete_edit_bone(splineik)

        #   control
        for i in range(1, amount + 2):
            id = '%02d' % (i)
            c_name = "c_" + spline_name + "_" + id + side
            c = get_edit_bone(c_name)
            if c:
                delete_edit_bone(c)

        #   root
        root_name = "c_" + spline_name + "_root" + side
        root = get_edit_bone(root_name)
        if root:
            delete_edit_bone(root)

            # tip
        tip_name = "c_" + spline_name + "_tip" + side
        tip = get_edit_bone(tip_name)
        if tip:
            delete_edit_bone(tip)

            # curvy
        curvy_name = "c_" + spline_name + "_curvy" + side
        curvy = get_edit_bone(curvy_name)
        if curvy:
            delete_edit_bone(curvy)

            # stretch
        stretch = get_edit_bone(stretch_bone_name)
        if stretch:
            delete_edit_bone(stretch)
        
        # Curve
        nurbs_name = "spline_ik_curve" + side
        nurbs = ard.get_spline_ik(rig, side)

        if nurbs:
            delete_object(nurbs)

    def disable_bbones(context, side):
    
        bpy.ops.object.mode_set(mode='EDIT')

        bbones_name = "bbones"
        if sel_bone.keys():
            if "arp_bbones" in sel_bone.keys():
                bbones_name = sel_bone["arp_bbones"]

        # Bones
        # ref
        first_ref_bone = get_edit_bone(bbones_name + "_01_ref" + side)
        amount = first_ref_bone["bbones_count"]
        for i in range(1, amount + 1):
            id = '%02d' % (i)
            ref_name = bbones_name + "_" + id + "_ref" + side
            ref = get_edit_bone(ref_name)
            if ref:
                delete_edit_bone(ref)

        # bendy bones
        for i in range(1, amount + 1):
            id = '%02d' % (i)
            bbone_name = bbones_name + "_" + id + side
            bbone = get_edit_bone(bbone_name)
            if bbone:
                delete_edit_bone(bbone)

        # in
        for i in range(1, amount + 1):
            id = '%02d' % (i)
            bbone_name = bbones_name + "_in_" + id + side
            bbone = get_edit_bone(bbone_name)
            if bbone:
                delete_edit_bone(bbone)

        # out
        for i in range(1, amount + 1):
            id = '%02d' % (i)
            bbone_name = bbones_name + "_out_" + id + side
            bbone = get_edit_bone(bbone_name)
            if bbone:
                delete_edit_bone(bbone)

        # control
        for i in range(1, amount + 2):
            id = '%02d' % (i)
            c_name = "c_" + bbones_name + "_" + id + side
            c = get_edit_bone(c_name)
            if c:
                delete_edit_bone(c)

            # tip
            tip_name = "c_tip_" + bbones_name + "_" + id + side
            tip = get_edit_bone(tip_name)
            if tip:
                delete_edit_bone(tip)

    def disable_child_connections(current_bone):
        # check for existing parent connection
        for bone in bpy.context.active_object.data.edit_bones:
            if bone.layers[17]:
                if bone.parent == get_edit_bone(current_bone):
                    bone.parent = None

        if "bone" in locals():
            del bone

    def disable_bot(context):
        sides = ['.l', '.r']

        for _side in sides:
            bot_ref = get_edit_bone('c_bot_bend' + _side)
            bot_control = get_edit_bone('bot_bend_ref' + _side)
            if bot_ref:
                delete_edit_bone(bot_ref)
            if bot_control:
                delete_edit_bone(bot_control)
                if rig_add:
                    rig_add.data.bones['c_bot_bend' + _side].use_deform = False

                    # proxy picker
                proxyb = get_edit_bone('c_bot_bend_proxy' + _side)
                if proxyb:
                    switch_bone_layer(proxyb.name, 1, 22, False)
                else:
                    print("No bot proxy bones found, skip it")
            else:
                print("No bot_bend bone found, skip it")

    def disable_ear(side):
        for i in range(1, 17):
            id = '%02d' % i
            # control
            cont = get_edit_bone('c_ear_' + id + side)
            if cont:
                delete_edit_bone(cont)

            # proxy
            proxyb = get_edit_bone('c_ear_' + id + '_proxy' + side)
            if proxyb:
                delete_edit_bone(proxyb)

            # ref
            ref = get_edit_bone('ear_' + id + '_ref' + side)
            if ref:
                delete_edit_bone(ref)
    

    if limb_type == 'arm':
        arm_bones = ard.arm_bones
        arm_deform = ard.arm_deform
        arm_control = ard.arm_control
        arm_ref_list = ard.arm_ref_list
        fingers_list = ['thumb', 'index', 'middle', 'ring', 'pinky']
        fingers_control = ard.fingers_control
        fingers_control_ik = ard.fingers_control_ik
    
        # remove drivers upfront to avoid crashes when removing bones
        drivers = rig.animation_data.drivers
        removed_bones_drivers = []
        for bn in arm_bones+fingers_control_ik:
            bname = bn+side
            for dr in drivers:
                if dr.data_path.startswith('pose.bones'):
                    dr_bname = dr.data_path.split('"')[1]

                    if bname == dr_bname:
                        rig.animation_data.drivers.remove(dr)
                        if not bname in removed_bones_drivers:
                            removed_bones_drivers.append(bname)    

        # remove feathers if any
        set_arm_feathers(False, 1, 1, 1, 1, 1, False, True, False, side)

        bpy.ops.armature.select_all(action='DESELECT')

        # delete control, mechanic and deform bones
        for bname in arm_bones+fingers_control_ik:
            eb = get_edit_bone(bname+side)
            if eb:
                delete_edit_bone(eb)

        # delete additional twist secondary controllers
        for idx in range(1, 33):
            bone_names = ["arm_twist_"+str(idx)+side, "forearm_twist_"+str(idx)+side, "c_arm_twist_"+str(idx)+side, "c_forearm_twist_"+str(idx)+side, "arm_segment_"+str(idx)+side, "forearm_segment_"+str(idx)+side]
            for bname in bone_names:
                b = get_edit_bone(bname)
                if b:
                    delete_edit_bone(b)

        for b_name in ["c_forearm_stretch"+side, "c_arm_stretch"+side, "c_forearm_twist"+side, "c_arm_twist"+side, "arm_bendy"+side, "forearm_bendy"+side, "arm_twt_offset"+side, "arm_str_offset"+side, "forearm_str_offset"+side, "arm_twist"+side, "forearm_twist"+side]:
            b = get_edit_bone(b_name)
            if b:
                delete_edit_bone(b)

        # delete proxy bones
        for bone in arm_control + fingers_control:
            eb = get_edit_bone(bone + '_proxy' + side)
            if eb:
                delete_edit_bone(eb)

        for bone in arm_ref_list:
            eb = get_edit_bone(bone + side)
            if eb:
                delete_edit_bone(eb)

        # delete visibility property
        cpos = get_pose_bone('c_pos')
        if cpos and '_dupli' in sel_bone_name:
            if len(cpos.keys()) > 0:
                if 'arm ' + sel_bone_name[-5:] in cpos.keys():
                    del cpos['arm ' + sel_bone_name[-5:]]

        # delete rig_add bones
        if rig_add:
            unhide_object(rig_add)
            edit_rig(rig_add)
            bpy.ops.armature.select_all(action='DESELECT')

            # delete
            for b_add in ard.arm_bones_rig_add:
                b_add_bone = get_edit_bone(b_add + side)
                if b_add_bone:
                    delete_edit_bone(b_add_bone)

                    # delete
            bpy.ops.object.mode_set(mode='OBJECT')
            bpy.ops.object.select_all(action='DESELECT')
            hide_object(rig_add)

            edit_rig(rig)
    
    elif limb_type == 'leg':
        leg_bones_list = ard.leg_bones_list
        leg_control = ard.leg_control
        leg_ref_bones_list = ard.leg_ref_bones_list
        toes_list = ['thumb', 'index', 'middle', 'ring', 'pinky']
        toes_control = ard.toes_control
    
        # remove drivers upfront to avoid crashes when removing bones
        drivers = rig.animation_data.drivers
        removed_bones_drivers = []
        for bn in leg_bones_list:
            bname = bn+side
            for dr in drivers:
                if dr.data_path.startswith('pose.bones'):
                    dr_bname = dr.data_path.split('"')[1]

                    if bname == dr_bname:
                        rig.animation_data.drivers.remove(dr)
                        if not bname in removed_bones_drivers:
                            removed_bones_drivers.append(bname)

        print("Removed drivers for bones:")
        for i in removed_bones_drivers:
            print(i)

        # main control, deforming and mechanic bones
        bpy.ops.armature.select_all(action='DESELECT')
        for bname in leg_bones_list:
            eb = get_edit_bone(bname+side)
            if eb:
                delete_edit_bone(eb)

        # additional twist secondary controllers
        for idx in range(1, 33):
            bone_names = ["thigh_twist_"+str(idx)+side, "leg_twist_"+str(idx)+side, "c_thigh_twist_"+str(idx)+side, "c_leg_twist_"+str(idx)+side, "thigh_segment_"+str(idx)+side, "leg_segment_"+str(idx)+side]
            for bname in bone_names:
                b = get_edit_bone(bname)
                if b:
                    delete_edit_bone(b)

        for b_name in ["c_leg_stretch"+side, "c_thigh_stretch"+side, "c_leg_twist"+side, "c_thigh_twist"+side, "thigh_bendy"+side, "leg_bendy"+side, "thigh_twt_offset"+side, "thigh_str_offset"+side, "leg_str_offset"+side, "thigh_twist"+side, "leg_twist"+side]:
            b = get_edit_bone(b_name)
            if b:
                delete_edit_bone(b)

        # ref bones
        for bname in leg_ref_bones_list:
            eb = get_edit_bone(bname + side)
            if eb:
                delete_edit_bone(eb)

        # proxy bones
        for bname in leg_control + toes_control:
            eb = get_edit_bone(bname+'_proxy'+side)
            if eb:
                delete_edit_bone(eb)

        # rig_add bones
        if rig_add:
            unhide_object(rig_add)
            edit_rig(rig_add)
            bpy.ops.armature.select_all(action='DESELECT')

            # delete
            for b_add in ard.leg_bones_rig_add:
                b_add_bone = get_edit_bone(b_add + side)
                if b_add_bone:
                    delete_edit_bone(b_add_bone)

            bpy.ops.object.mode_set(mode='OBJECT')
            bpy.ops.object.select_all(action='DESELECT')
            hide_object(rig_add)

        edit_rig(rig)

        # delete visibility property
        cpos = get_pose_bone('c_pos')
        if cpos and '_dupli' in sel_bone_name:
            if len(cpos.keys()) > 0:
                prop_name = 'leg '+sel_bone_name[-5:]
                if prop_name in cpos.keys():
                    del cpos[prop_name]
    
    elif limb_type == 'facial':
        for basename in ard.facial_ref:
            bname = basename + side
            if ".x" in basename:
                bname = basename[:-2] + side     

            if sel_bone_name == bname:
                disable_facial(side)
                break

    elif limb_type == 'head':
        disable_head(context, side)
   
    elif limb_type == 'neck':
        set_neck(1)
        disable_head(context, side)
        disable_neck(context, side)

    elif limb_type == 'spine':
        
        spine_bones_list = ard.spine_bones + ard.spine_ref_list
        for i in range(3, rig.rig_spine_count):
            str_idx = '%02d' % i
            for spine_03_name in ard.spine03_deform + ard.spine03_control + ['spine_03_ref.x'] + ard.spine_03_intern:
                #spine_bones_list.append(spine_03_name)
                spine_bones_list.append(spine_03_name.replace('03', str_idx))
        
        for bname in spine_bones_list:
            ebone = get_edit_bone(bname)
            if ebone:
                delete_edit_bone(ebone)

        # rig_add bones
        if rig_add:
            unhide_object(rig_add)
            edit_rig(rig_add)
            bpy.ops.armature.select_all(action='DESELECT')

            # delete
            for b_add in ard.spine_bones_rig_add:
                b_add_bone = get_edit_bone(b_add)
                if b_add_bone:
                    delete_edit_bone(b_add_bone)

            bpy.ops.object.mode_set(mode='OBJECT')
            bpy.ops.object.select_all(action='DESELECT')
            
            hide_object(rig_add)
            edit_rig(rig)
    
    elif limb_type == 'spline_ik':
        print("disable spline...")
        disable_spline_ik(context, side)
  
    elif limb_type == 'bbones':
        disable_bbones(context, side)

    elif limb_type == 'tail':
        set_tail(False)
        
    elif limb_type == 'breast':
        set_breast(False)

    elif limb_type == 'ear':
        disable_ear(side)

    elif limb_type == 'bot':
        disable_bot(context)
    
        
    # Finalize
    # Remove unused drivers
    remove_invalid_drivers()

    # Select at least one bone to avoid the pop up effect of the panel
    if len(get_selected_edit_bones()) == 0:
        if get_edit_bone('root_ref.x'):
            rig.data.edit_bones.active = get_edit_bone('root_ref.x')
        elif get_edit_bone('c_pos'):
            rig.data.edit_bones.active = get_edit_bone('c_pos')
        elif len(rig.data.edit_bones):
            rig.data.edit_bones.active = rig.data.edit_bones[0]

    # Display reference layer only
    rig.data.layers[17] = True
    for i in range(0, 32):
        if i != 17:
            rig.data.layers[i] = False

    # Restore mirror edit
    rig.data.use_mirror_x = mirror_edit
    
    # end disable_limb()


def _pick_bone():
    bpy.context.scene.arp_driver_bone = bpy.context.active_object.data.bones.active.name


def _create_driver():
    obj_mesh = get_selected_pair(1)
    rig = get_selected_pair(2)
    shape_keys = obj_mesh.data.shape_keys.key_blocks
    shape_index = bpy.context.active_object.active_shape_key_index

    # create driver
    new_driver = shape_keys[shape_index].driver_add("value")
    new_driver.driver.expression = "var"
    new_var = new_driver.driver.variables.new()
    new_var.type = 'TRANSFORMS'
    new_var.targets[0].id = rig
    new_var.targets[0].bone_target = bpy.context.scene.arp_driver_bone

    new_var.targets[0].transform_type = bpy.context.scene.arp_driver_transform
    new_var.targets[0].transform_space = 'LOCAL_SPACE'


def _set_shape_key_driver(self, value):
    autokey_state = bpy.context.scene.tool_settings.use_keyframe_insert_auto
    bpy.context.scene.tool_settings.use_keyframe_insert_auto = False

    obj = bpy.context.active_object
    shape_keys = obj.data.shape_keys.key_blocks
    shape_index = bpy.context.active_object.active_shape_key_index
    var = None
    shape_key_driver = None
    shape_key_name = shape_keys[shape_index].name

    try:
        drivers_list = obj.data.shape_keys.animation_data.drivers
        for dr in drivers_list:
            if shape_key_name == dr.data_path.split('"')[1]:
                shape_key_driver = dr
    except:
        self.report({'ERROR'}, "No driver found for the selected shape key")
        return

    if shape_key_driver == None:
        self.report({'ERROR'}, "No driver found for the selected shape key")
        return

    # Currently only drivers with one variable, easy to evaluate, can be processed
    # because it doesn't seem possible to evaluate via the API the output driver value, or variable value
    # the value must be re-evaluated manually
    if len(shape_key_driver.driver.variables) > 1:
        self.report({'ERROR'}, "Invalid driver: only drivers made of 1 variable are valid")
        return

    # remove the fcurve modifier
    if len(shape_key_driver.modifiers) > 0:
        shape_key_driver.modifiers.remove(shape_key_driver.modifiers[0])

    bpy.ops.transform.translate(value=(0, 0, 0))# update hack
    
    # create keyframe
    if value != 'reset':
        # get the bone driver
        driver_var = shape_key_driver.driver.variables[0]
        driver_value = None# the driver value
        
        # 1.Transform type variable case
        if driver_var.type == "TRANSFORMS":
            bone_driver_name = driver_var.targets[0].bone_target
            armature = driver_var.targets[0].id
            bone_driver = armature.pose.bones[bone_driver_name]
            transform_type = driver_var.targets[0].transform_type

            if transform_type == 'LOC_X':
                var = bone_driver.location[0]
            if transform_type == 'LOC_Y':
                var = bone_driver.location[1]
            if transform_type == 'LOC_Z':
                var = bone_driver.location[2]

            if transform_type == 'ROT_X':
                var = bone_driver.rotation_euler[0]
            if transform_type == 'ROT_Y':
                var = bone_driver.rotation_euler[1]
            if transform_type == 'ROT_Z':
                var = bone_driver.rotation_euler[2]

            if transform_type == 'SCALE_X':
                var = bone_driver.scale[0]
            if transform_type == 'SCALE_Y':
                var = bone_driver.scale[1]
            if transform_type == 'SCALE_Z':
                var = bone_driver.scale[2]

        # 2. Rotational difference variable case
        elif driver_var.type == "ROTATION_DIFF":
            armature = driver_var.targets[0].id
            b1_name = driver_var.targets[0].bone_target
            b2_name = driver_var.targets[1].bone_target
            b1 = armature.pose.bones.get(b1_name)
            b2 = armature.pose.bones.get(b2_name)
            var = b1.y_axis.angle(b2.y_axis)

        # 3. Property variable case
        elif driver_var.type == "SINGLE_PROP":
            armature = driver_var.targets[0].id
            dp = armature.name + '.' + driver_var.targets[0].data_path
            locals()[armature.name] = armature# to create the armature variable name with its actual name, before being evaluated => e.g. rig = armature
            var = eval(dp)
            
        # evaluate the expression
        driver_value = var
        if shape_key_driver.driver.type == 'SCRIPTED':
            if shape_key_driver.driver.expression != 'var':   
                try:
                    driver_value = eval(shape_key_driver.driver.expression)                
                    print("driver value", driver_value)
                except:
                    # division by 0
                    print('Error, division by 0')
                    driver_value = 0.0
        
        # first pass the X point value as an extreme value, since a bug prevents to set it a the right value when using small values (0.01)
        keyf = shape_key_driver.keyframe_points.insert(1000000000, float(value))
        #keyf = shape_key_driver.keyframe_points.insert(driver_value, float(value), options={'FAST'})
        # then correct it
        print("SET KEYF X", driver_value)
        keyf.co[0] = driver_value        

        # remove any previous keyframe at the same location
        for key in shape_key_driver.keyframe_points:
            if key.co[0] == keyf.co[0] and key.co[1] != keyf.co[1]:
                shape_key_driver.keyframe_points.remove(key)

        keyf.interpolation = 'LINEAR'

        # check if 1st key created
        first_key_created = False
        for key in shape_key_driver.keyframe_points:
            if round(key.co[0], 3) == 0:
                first_key_created = True

        if not first_key_created:
            print("Create first key")
            keyf = shape_key_driver.keyframe_points.insert(10000000000, 0.00)
            keyf.co[0] = 0.0
            keyf.interpolation = 'LINEAR'

        # update fcurve
        shape_key_driver.update()


    else:# reset the driver curve
        print('reset')
        # remove all keyframe points
        while len(shape_key_driver.keyframe_points) > 0:
            shape_key_driver.keyframe_points.remove(shape_key_driver.keyframe_points[0], fast=True)
        '''
        # create two linear points
        # disable it for now, seems it's no more necessary
        keyf1 = shape_key_driver.keyframe_points.insert(0.0, 0.0)
        keyf1.interpolation = 'LINEAR'
        keyf2 = shape_key_driver.keyframe_points.insert(1.0, 1.0)
        keyf2.interpolation = 'LINEAR'
        '''
        # add modifier
        shape_key_driver.modifiers.new(type="GENERATOR")
        """
        _id = shape_key_driver.driver.variables[0].targets[0].id
        _bone_target = shape_key_driver.driver.variables[0].targets[0].bone_target
        _transform_type = shape_key_driver.driver.variables[0].targets[0].transform_type
        _transform_space = shape_key_driver.driver.variables[0].targets[0].transform_space
        _expression = shape_key_driver.driver.expression
        print(_expression)

        # delete driver
        obj.data.shape_keys.driver_remove(shape_key_driver.data_path, -1)

        # create new one from old one
        new_driver = shape_keys[shape_index].driver_add("value")
        new_driver.driver.expression = _expression
        new_var = new_driver.driver.variables.new()
        new_var.type = 'TRANSFORMS'
        new_var.targets[0].id = _id
        new_var.targets[0].bone_target = _bone_target
        new_var.targets[0].transform_type = _transform_type
        new_var.targets[0].transform_space = _transform_space
        """
    bpy.ops.transform.translate(value=(0, 0, 0))# update hack
    
    # restore autokey state
    bpy.context.scene.tool_settings.use_keyframe_insert_auto = autokey_state


def get_next_dupli_id(side, bone_type, mirror=False):
    # returns the next limb duplication id
    limb_id = 0
    found_base = False
    ideal_side = side
    ideal_side_taken = False
    side = side[-2:]# end characters only
    rig = bpy.context.active_object
    
    for _bone in rig.data.edit_bones:
        # arms
        if 'shoulder' in bone_type or 'arm' in bone_type or 'finger' in bone_type:        
            if "shoulder_ref"+side == _bone.name:
                found_base = True
            if "shoulder_ref_dupli_" in _bone.name and _bone.name[-2:] == side:
                current_id = int(float(_bone.name[-5:-2]))
                if current_id > limb_id:
                    limb_id = current_id
            if _bone.name == 'shoulder_ref'+ideal_side:
                ideal_side_taken = True
            
        # legs
        elif 'thigh' in bone_type or 'leg' in bone_type or 'toes' in bone_type:
            if "thigh_ref"+side == _bone.name:
                found_base = True
            if "thigh_ref_dupli" in _bone.name and _bone.name[-2:] == side:
                current_id = int(float(_bone.name[-5:-2]))
                if current_id > limb_id:
                    limb_id = current_id
            if _bone.name == 'thigh_ref'+ideal_side:
                ideal_side_taken = True
                
        # heads
        elif 'neck' in bone_type or 'head' in bone_type:
            if "neck_ref"+side == _bone.name:
                found_base = True

            if "neck_ref_dupli" in _bone.name and _bone.name[-2:] == side:
                current_id = int(float(_bone.name[-5:-2]))
                if current_id > limb_id:
                    limb_id = current_id
            if _bone.name == 'neck_ref'+ideal_side:
                ideal_side_taken = True        
            
        # ears
        elif 'ear' in bone_type:
            if "ear_01_ref" in _bone.name:
                found_base = True

            if "ear_01_ref_dupli" in _bone.name:
                current_id = int(float(_bone.name[-5:-2]))  # ear_01_ref_dupli_*001*.l => id = 1
             
                if current_id > limb_id:
                    limb_id = current_id
                  
        # spine
        elif 'spine' in bone_type:
            if "root_ref.x" in _bone.name:
                found_base = True
        # breast
        elif 'breast' in bone_type:
            if "breast_01_ref" in _bone.name:
                found_base = True
        # tail
        elif 'tail' in bone_type:        
            if len(_bone.keys()):
                if ("master_at_root" in _bone.keys() or _bone.name.startswith("tail_00_ref")) and "_ref" in _bone.name and _bone.name[-2:] == side:
                    found_base = True

                    if "_dupli" in _bone.name:
                        current_id = int(float(_bone.name[-5:-2]))# spline_01_ref_dupli_*001*.l => id = 1
                        if current_id > limb_id:
                            limb_id = current_id
                    if get_bone_side(_bone.name) == ideal_side:
                        ideal_side_taken = True
            
        # spline ik
        elif bone_type.startswith("spline_"):
            if len(_bone.keys()):
                if ("spline_name" in _bone.keys() or "spline_01_ref" in _bone.name) and "_ref" in _bone.name and _bone.name[-2:] == side:
                    found_base = True

                    if "_dupli" in _bone.name:
                        current_id = int(float(_bone.name[-5:-2]))# spline_01_ref_dupli_*001*.l => id = 1
                        if current_id > limb_id:
                            limb_id = current_id
                            
                    if get_bone_side(_bone.name) == ideal_side:
                        ideal_side_taken = True
                          

        # bendy bones
        elif bone_type.startswith("bbones"):
            if len(_bone.keys()):
                if ("bbones_name" in _bone.keys() or "bbones_01_ref" in _bone.name) and "_ref" in _bone.name and _bone.name[-2:] == side:
                    found_base = True

                    if "_dupli" in _bone.name:
                        current_id = int(float(_bone.name[-5:-2]))# bbones_01_ref_dupli_*001*.l => id = 1
                        if current_id > limb_id:
                            limb_id = current_id
                            
                    if get_bone_side(_bone.name) == ideal_side:
                        ideal_side_taken = True
        

    dupli_id = '{:03d}'.format(limb_id + 1)# 1 => 002
    #print('mirror', mirror, 'ideal_side_taken', ideal_side_taken, 'ideal_side', ideal_side)
    if mirror and ideal_side_taken == False:# if mirror, we want ideally the same dupli ID for the mirrorred side
        dupli_id = ideal_side[-5:-2]
        found_base = 'dupli' in ideal_side
       
    return dupli_id, found_base


def _dupli_limb(dupli_mirror=False):   
    rig_name = bpy.context.active_object.name
    rig = get_object(rig_name)    
    rig_add = get_rig_add(rig)
    if rig_add:
        unhide_object(rig_add)
        rig_add_name = rig_add.name

    # display all layers
    layers_select = enable_all_armature_layers()

    # disable the proxy picker to avoid bugs  
    proxy_picker_state = disable_proxy_picker()

    # disable x-mirror to avoid bugs
    mirror_x_state = rig.data.use_mirror_x
    rig.data.use_mirror_x = False

    arm_bones = ard.arm_bones
    arm_ref_list = ard.arm_ref_list
    arm_bones_rig_add = ard.arm_bones_rig_add
    leg_bones_list = ard.leg_bones_list
    leg_ref_bones_list = ard.leg_ref_bones_list
    leg_bones_rig_add = ard.leg_bones_rig_add
    spline_name = "spline"
    bbones_name = "bbones"

    sel_bone = get_selected_edit_bones()[0]
    selected_bones_names = [b.name for b in get_selected_edit_bones()]

    sides = [".l", ".r"]
    
    symmetrical = False
    
    def rename_node_side(node):
        # rename to the new bone side after duplication        
        bname = trim_dupli_name(node.name)# trim .001
       
        if dupli_mirror and not symmetrical:# symmetrical limbs (facial) containing both left and right sides can't be mirrored for now
            dupli_side = ''
            if found_base:
                dupli_side = '_dupli_' + dupli_id
            new_side = dupli_side + get_opposite_side(side)[-2:]                    
        else:       
            dupli_side = '_dupli_' + dupli_id
            new_side = dupli_side + bname[-2:]
      
        node.name = retarget_bone_side(bname, new_side)        
        
    
    def duplicate_ref(limb, side, dupli_id, found_base):        
        # get the bones list
        if limb == 'arm':
            bone_list = ard.arm_ref_list
            
        elif limb == 'leg':
            bone_list = ard.leg_ref_bones_list
            
        elif limb == 'head':
            bone_list = ard.facial_ref \
                        + ard.get_variable_eyelids(side, type='REFERENCE', no_side=True) \
                        + ard.get_variable_lips(side, type='REFERENCE', no_side=True) \
                        + ["head_ref.x", "neck_ref.x"]
                        
        elif limb == "ear":
            bone_list = []
            for i in range(1, 64):
                id = '%02d' % i
                ref_name = "ear_" + id + "_ref" + side                
                if get_edit_bone(ref_name):                    
                    bone_list.append("ear_" + id + "_ref")
                    
        elif limb == "spline_ik":
            spline_amount = get_edit_bone(spline_name + "_01_ref" + side)["spline_count"]
            bone_list = []
            for i in range(1, spline_amount + 1):
                id = '%02d' % i
                ref_name = spline_name + "_" + id + "_ref"
                bone_list.append(ref_name)
                
        elif limb == "bbones":
            bbones_amount = get_edit_bone(bbones_name + "_01_ref" + side)["bbones_count"]
            bone_list = []
            for i in range(1, bbones_amount + 1):
                id = '%02d' % i
                ref_name = bbones_name + "_" + id + "_ref"
                bone_list.append(ref_name)
                
        elif limb == 'tail':
            bone_list = []
            tail_count = get_tail_count(side)
            for i in range(0, tail_count):
                id = '%02d' % i
                ref_name = 'tail_' + id + "_ref"
                bone_list.append(ref_name)
            
        # Select bones
        bpy.ops.armature.select_all(action='DESELECT')

        _sides = [side]
        if symmetrical:# limbs containing left and right bones such as facial bones
            _sides = [side[:-2] + ".l", side[:-2] + ".r"]
            

        for base_name in bone_list:
            for _side in _sides:
                if base_name[-2:] == ".x":
                    bname = base_name[:-2] + _side[:-2] + ".x"
                else:
                    bname = base_name + _side

                ref_bone = get_edit_bone(bname)
                
                if ref_bone:
                    if ref_bone.layers[22] == False:# if not disabled
                        ref_bone.select = True
                    
                elif bpy.context.scene.arp_debug_mode:
                    print(bname, "not found for duplication")

        bpy.ops.object.mode_set(mode='POSE')
        bpy.ops.object.mode_set(mode='EDIT')# debug selection

        # Duplicate
        duplicate(type="EDIT_BONE")

        # Rename
        for ebone in get_selected_edit_bones():            
            rename_node_side(ebone)
            
            # mirror limb side prop
            prop_names_side = ['tail_side', 'bbones_side', 'spline_side']
            for prop_name in prop_names_side:
                if prop_name in ebone.keys():
                    if ebone[prop_name] == '.r':
                        ebone[prop_name] = '.l'
                    elif ebone[prop_name] == '.l':
                        ebone[prop_name] = '.r'
                      
        if dupli_mirror and not symmetrical:
            mirror_bones_transforms(get_selected_edit_bones())
            
        # end duplicate_ref()
         

    def duplicate_rig(limb, side, dupli_id, found_base):      

        if limb == 'arm':
            limb_bones_list = arm_bones + ard.fingers_control_ik
            limb_control = ard.arm_control + ard.fingers_control + ard.fingers_control_ik
            limb_bones_rig_add = arm_bones_rig_add
            bones_drivers_key = ['hand', 'arm']

            for fing_type in ["thumb", "index", "middle", "ring", "pinky"]:
                bones_drivers_key.append("c_"+fing_type)
                bones_drivers_key.append("c_"+fing_type)

        elif limb == 'leg':
            limb_bones_list = leg_bones_list
            limb_control = ard.leg_control + ard.toes_control
            limb_bones_rig_add = leg_bones_rig_add
            bones_drivers_key = ['leg', 'thigh', 'foot', 'toes']

        elif limb == 'head':            
            limb_bones_list = ard.facial_bones + ard.head_bones + ard.neck_bones \
                            + ard.get_variable_lips(side, type='NON_REF', no_side=True) \
                            + ard.get_variable_eyelids(side, type='CONTROLLER', no_side=True)
            limb_control = ard.head_control + ard.facial_control + ard.neck_control# for picker duplication
            bones_drivers_key = ['c_eye', 'c_lips_', 'jaw_ret_', 'head_scale']
            limb_bones_rig_add = None         

        elif limb == 'ear':
            limb_bones_list = []# ard.ear_control
            for i in range(1, 64):
                id = '%02d' % i
                c_name = "c_ear_" + id + side
                #print("CNAME", c_name)
                if get_edit_bone(c_name):
                    #print(c_name, "added to bone list")
                    limb_bones_list.append("c_ear_" + id)

            limb_control = limb_bones_list.copy()  # ard.ear_control
            bones_drivers_key = []
            limb_bones_rig_add = None

        elif limb == "spline_ik":
            spline_1_ref = get_edit_bone(spline_name + "_01_ref" + side)
            spline_amount = spline_1_ref["spline_count"]
            spline_type = '1'
            if "spline_type" in spline_1_ref.keys():
                spline_type = spline_1_ref["spline_type"]

            limb_bones_list_generic = ard.spline_ik_bones
            limb_bones_list = []
            for i in limb_bones_list_generic:
                limb_bones_list.append(i.replace("spline", spline_name))

            limb_control = []
            # add controllers
            for i in range(1, spline_amount+2):
                id = '%02d' % i
                limb_bones_list.append("c_" + spline_name + "_" + id)

                if spline_type == '2':
                    limb_bones_list.append("c_" + spline_name + "_master_" + id)
                    limb_bones_list.append("c_" + spline_name + "_inter_" + id)

            # add splineik chain
            for i in range(1, spline_amount + 2):
                id = '%02d' % i
                limb_bones_list.append(spline_name + "_" + id)

            spline_ik_curve_name = "spline_ik_curve" + side
            limb_bones_rig_add = None
            bones_drivers_key = [spline_name+'_']

        elif limb == "bbones":
            bbones_1_ref = get_edit_bone(bbones_name + "_01_ref" + side)
            bbones_amount = bbones_1_ref["bbones_count"]
            limb_bones_list = []
            limb_control = []
            limb_bones_rig_add = None

            for i in range(1, bbones_amount+2):
                id = '%02d' % i
                # controls
                limb_bones_list.append("c_" + bbones_name + "_" + id)
                # in
                limb_bones_list.append(bbones_name + "_in_" + id)
                # out
                limb_bones_list.append(bbones_name + "_out_" + id)
                # bbone
                limb_bones_list.append(bbones_name + "_" + id)
            # tip
            limb_bones_list.append("c_tip_" + bbones_name + "_" + '%02d' % bbones_amount)

            bones_drivers_key = []
            
        elif limb == 'tail':
            limb_bones_rig_add = None
            bones_drivers_key = []
            limb_bones_list = []
            limb_control = ard.tail_bones
            tail_count = get_tail_count(side)
            # controls
            for i in range(0, tail_count):
                id = '%02d' % i
                limb_bones_list.append("c_tail_"+ id)
                
            # control master
            for n in limb_control:
                limb_bones_list.append(n)
            
        drivers_data = rig.animation_data.drivers
        

        def duplicate_limb_drivers(drivers_list):            
            bpy.ops.object.mode_set(mode='OBJECT')# necessary to switch mode to update the bones data (Blender 3.3)
            bpy.ops.object.mode_set(mode='EDIT')
            
            trim = 0
            dr_list_copy = [dr for dr in drivers_list]
            
            for dr in dr_list_copy:                
                if dr.data_path.partition('.')[0] == 'pose':# pose.bones["...
                    trim = 12
                else:# armature type
                    trim = 7

                string = dr.data_path[trim:]
                dp_bone_name = string.partition('"')[0]
                
                # only copy from original bone drivers, not dupli
                if '_dupli_' in dp_bone_name:
                    continue
                
                # Do not create a driver if the dupli bone does not exist
                dupli_bone_name = ''
                
                if dupli_mirror and not symmetrical:
                    dupli_side = ''
                    if found_base:
                        dupli_side = '_dupli_' + dupli_id
                    dupli_bone_name = dp_bone_name[:-2] + dupli_side + get_opposite_side(side[-2:])
                else:
                    dupli_bone_name = dp_bone_name[:-2] + '_dupli_' + dupli_id + dp_bone_name[-2:]

                if get_data_bone(dupli_bone_name) == None:                    
                    continue
                
                is_limb = False
                for limb_name in bones_drivers_key:
                    if limb_name in dp_bone_name or ('bend_all' in dp_bone_name and limb == 'arm'):
                        is_limb = True

                side_check = True
                if limb == "head":
                    side_check = False

                # create new driver                
                if is_limb and (dp_bone_name.endswith(side[-2:]) or not side_check):
                    new_driver = drivers_list.from_existing(src_driver=drivers_list.find(dr.data_path))
                    
                    if new_driver == None:
                        new_driver = drivers_list.from_existing(src_driver=drivers_list.find(dr.data_path, index=dr.array_index))

                    # set array index
                    try:
                        new_driver.array_index = dr.array_index
                    except:
                        pass

                    # change data path
                    if 'foot_pole' in dp_bone_name:# can't create driver with 'from_existing' for foot pole Y location, bug?
                        new_driver = rig.driver_add("location", dr.array_index)
                        new_driver.data_path = dr.data_path.replace(dp_bone_name, dupli_bone_name)
                        new_driver.driver.expression = dr.driver.expression

                        for v in dr.driver.variables:
                            v1 = new_driver.driver.variables.new()
                            v1.type = v.type
                            v1.name = v.name
                            try:
                                v1.targets[0].data_path = v.targets[0].data_path
                                v1.targets[0].id_type = v.targets[0].id_type
                                v1.targets[0].id = v.targets[0].id
                            except:
                                print("no data_path for foot_pole variable: " + v1.name)
                    else:
                        new_driver.data_path = dr.data_path.replace(dp_bone_name, dupli_bone_name)

                    # change variable path
                    for v1 in new_driver.driver.variables:
                        try:
                            string = v1.targets[0].data_path[12:]
                            dp_bone_name = string.partition('"')[0]
                            bone_target_name = ''
                            
                            if dupli_mirror and not symmetrical:
                                dupli_side = ''
                                if found_base:
                                    dupli_side = '_dupli_' + dupli_id                                    
                                bone_target_name = retarget_bone_side(dp_bone_name, dupli_side + get_opposite_side(side[-2:]))# shoulder.l > shoulder + _dupli_001 + .r                              
                            else:
                                bone_target_name = retarget_bone_side(dp_bone_name, '_dupli_' + dupli_id + dp_bone_name[-2:])
                                
                            new_dp = v1.targets[0].data_path.replace(dp_bone_name, bone_target_name)
                                
                            v1.targets[0].data_path = new_dp
                        except:
                            print("Warning, data_path error for: " + v1.name)

                    # force drivers to refresh because of bones name change, udpate issues otherwise
                    # switch mode as a hack to force drivers update dependencies
                    bpy.ops.object.mode_set(mode='OBJECT')
                    bpy.ops.object.mode_set(mode='EDIT')
                    new_driver.driver.expression += ' '
                    new_driver.driver.expression = new_driver.driver.expression[:-1]
                    
                    # mirror side specifics
                    if dupli_mirror and not symmetrical: 
                        if limb == 'arm':
                            # IK pole angle direction
                            if 'constraints["IK"].pole_angle' in new_driver.data_path:
                                if len(new_driver.keyframe_points) >= 2:                                    
                                    kp1 = new_driver.keyframe_points[0]
                                    kp2 = new_driver.keyframe_points[1]
                                    if get_opposite_side(side[-2:]) == '.l':
                                        kp1.co[0] = -2.0
                                        kp2.co[0] = 2.0
                                    elif get_opposite_side(side[-2:]) == '.r':
                                        kp1.co[0] = 0.0
                                        kp2.co[0] = 4.0
                                        
                        elif limb == 'leg':
                            # IK pole angle direction
                            if 'constraints["IK"].pole_angle' in new_driver.data_path:
                                if len(new_driver.keyframe_points) >= 2:                                    
                                    kp1 = new_driver.keyframe_points[0]
                                    kp2 = new_driver.keyframe_points[1]
                                    if get_opposite_side(side[-2:]) == '.l':
                                        kp1.co[0] = -2.0
                                        kp2.co[0] = 2.0
                                    elif get_opposite_side(side[-2:]) == '.r':
                                        kp1.co[0] = 0.0
                                        kp2.co[0] = 4.0
                                        

        # select bones
        bpy.ops.armature.select_all(action='DESELECT')

        _sides = [side]
        if symmetrical:  # limbs containing left and right bones such as facial bones
            _sides = [side[:-2] + ".l", side[:-2] + ".r"]

        for base_name in limb_bones_list:
            for _side in _sides:
                if base_name[-2:] == ".x":
                    bname = base_name[:-2] + _side[:-2] + ".x"
                else:
                    bname = base_name + _side
                ebone = get_edit_bone(bname)
                if ebone:
                    if ebone.layers[22] == False:  # if not disabled (finger, toes...)
                        ebone.select = True
                elif bpy.context.scene.arp_debug_mode:
                    print(bname, "not found for duplication")

        bpy.ops.object.mode_set(mode='POSE')
        bpy.ops.object.mode_set(mode='EDIT')  # debug selection
        
        # duplicate
        duplicate(type="EDIT_BONE")

        selected_bones_names = []

        # Rename
        for ebone in get_selected_edit_bones():         
            rename_node_side(ebone)                
            selected_bones_names.append(ebone.name)
                
        if dupli_mirror and not symmetrical:
            # mirror transforms if dupli mirror
            mirror_bones_transforms(get_selected_edit_bones())

        # spline IK curve
        if limb == "spline_ik":          
            # duplicate curve object
            nurbs = ard.get_spline_ik(rig, side)
            
            bpy.ops.object.mode_set(mode='OBJECT')
            bpy.ops.object.select_all(action='DESELECT')
       
            unhide_object(nurbs)
            set_active_object(spline_ik_curve_name)
            duplicate(type="OBJECT")
            
            # rename
            spline_dupli = get_object(bpy.context.active_object.name)
            
            rename_node_side(spline_dupli)
            new_spline_ik_name = spline_dupli.name
         
            bpy.ops.object.select_all(action='DESELECT')
            set_active_object(rig_name)
            hide_object(nurbs)

            bpy.ops.object.mode_set(mode='POSE')
      
            # remap spline IK constraint curve
            id = '%02d' % spline_amount
            new_ik_pbone_name = ''
            if dupli_mirror:
                dupli_side = ''
                if found_base:
                    dupli_side = '_dupli_' + dupli_id          
                new_ik_pbone_name = spline_name + "_" + id + dupli_side + get_opposite_side(side[-2:])
            else:
                new_ik_pbone_name = spline_name + "_" + id + '_dupli_' + dupli_id + side[-2:]
             
            spline_ik_pbone = get_pose_bone(new_ik_pbone_name)
            spline_ik_pbone.constraints.get("Spline IK").target = get_object(new_spline_ik_name)

        if limb == "arm":
            # Delete fingers action constraints (fingers fist) if any
            for bname in selected_bones_names:
                if bname.startswith("c_thumb") or bname.startswith("c_index") or bname.startswith(
                        "c_middle") or bname.startswith("c_ring") or bname.startswith("c_pinky"):
                    if len(get_pose_bone(bname).constraints):
                        for cns in get_pose_bone(bname).constraints:
                            if cns.type == "ACTION":
                                get_pose_bone(bname).constraints.remove(cns)
                                
        # mirror settings (pose mode)
        if dupli_mirror and not symmetrical:
            for bname in selected_bones_names:
                # custom shapes
                pb = get_pose_bone(bname)
                if pb.custom_shape:
                    trimmed_cs_name = trim_dupli_name(pb.custom_shape.name)# there may be .001...
                    mirrored_shape_name = trimmed_cs_name[:-2] + get_opposite_side(trimmed_cs_name[-2:])
                    mirrored_shape = get_object(mirrored_shape_name)
                    if mirrored_shape:
                        pb.custom_shape = mirrored_shape     

                # bone color groups
                if pb.bone_group:
                    mirrored_grp_name = pb.bone_group.name[:-2] + get_opposite_side(pb.bone_group.name[-2:])
                    mirrored_grp = rig.pose.bone_groups.get(mirrored_grp_name)
                    if mirrored_grp:
                        pb.bone_group = mirrored_grp
                        
            # constraints specifics
            if limb == "leg":
                foot_heel_name = ard.leg_bones_dict['foot']['foot_heel'] + get_opposite_side(side)
                print("foot_heel_name", foot_heel_name)
                foot_heel = get_pose_bone(foot_heel_name)
                
                for cns in foot_heel.constraints:
                    if cns.type == 'TRANSFORM':                    
                        cns.to_min_x_rot *= -1
                        cns.to_max_x_rot *= -1
                        
                foot_bank_01_name = ard.leg_bones_dict['foot']['bank_01'] + get_opposite_side(side)
                foot_bank_01 = get_pose_bone(foot_bank_01_name)
                foot_bank_02_name = ard.leg_bones_dict['foot']['bank_02'] + get_opposite_side(side)
                foot_bank_02 = get_pose_bone(foot_bank_02_name)
                
                for bank_pb in [foot_bank_01, foot_bank_02]:
                    for cns in bank_pb.constraints:
                        if cns.type == 'TRANSFORM':                    
                            cns.to_min_y_rot *= -1
                            cns.to_max_y_rot *= -1
                            
                        if cns.type == 'LIMIT_ROTATION': 
                            min_y_save = cns.min_y
                            cns.min_y = -cns.max_y
                            cns.max_y = -min_y_save
                        
                toes_end_name = ard.leg_bones_dict['toes']['toes_end'] + get_opposite_side(side)
                toes_end = get_pose_bone(toes_end_name)
                
                for cns in toes_end.constraints:
                    if cns.type == 'TRANSFORM':                    
                        cns.to_min_x_rot *= -1
                        cns.to_max_x_rot *= -1

        # Create drivers
        if len(bones_drivers_key):
            duplicate_limb_drivers(drivers_data)

        # --Proxy picker bones
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.armature.select_all(action='DESELECT')

        proxy_bones_found = False

        # Select       
        for bname in limb_control:            
            for _side in _sides:
                bname_real = bname
                bproxyname = ""
                if bname.endswith('.x'):
                    bproxyname = bname[:-2]+'_proxy.x'
                if not bname.endswith('.x'):                    
                    bproxyname = bname+'_proxy'+_side
                    bname_real = bname+_side
                    

                bproxy = get_edit_bone(bproxyname)
                b = get_edit_bone(bname_real)
                if bproxy and b:
                    if b.layers[22] == False:  # if not disabled (finger, toes...)
                        bproxy.select = True
                        proxy_bones_found = True

        bpy.ops.object.mode_set(mode='POSE')
        bpy.ops.object.mode_set(mode='EDIT')  # debug selection

        if proxy_bones_found:
            duplicate(type="EDIT_BONE")

            coef = 1
            axis = 0
            if side == '.r':
                coef = -1
            if limb == "head" or limb == "ear":
                coef *= -6
                axis = 2
            dupli_id_int = int(float(dupli_id))  # move offset for each dupli, get the limb id

            # Move
            bnames = [i.name for i in get_selected_edit_bones()]
            for bname in bnames:
                eb = get_edit_bone(bname)
                move_bone(eb.name, 0.26 * coef * dupli_id_int, axis)

            bpy.ops.object.mode_set(mode='POSE')
            
            # rename
            for bname in bnames:
                pb = get_pose_bone(bname)
                base_name = pb.name[:-4]# trim .001
                new_side = '_dupli_' + dupli_id + base_name[-2:]                
                pb.name = retarget_bone_side(base_name,  new_side, dupli_only=True)
                # set proxy bone
                pb['proxy'] = retarget_bone_side(pb['proxy'], new_side, dupli_only=True)
                
            bpy.ops.object.mode_set(mode='EDIT')
            
            
        # --Rig_add
        if limb_bones_rig_add and rig_add:
            edit_rig(rig_add)
            bpy.ops.armature.select_all(action='DESELECT')

            # disable x-axis mirror edit
            bpy.context.active_object.data.use_mirror_x = False
            
            # select bones
            for bone in limb_bones_rig_add:
                e_bone = get_edit_bone(bone + side)
                if e_bone == None:
                    continue
                e_bone.select = True

            bpy.ops.object.mode_set(mode='POSE')
            bpy.ops.object.mode_set(mode='EDIT')  # debug selection

            # duplicate
            duplicate(type="EDIT_BONE")

            # Rename
            for ebone in get_selected_edit_bones():
                rename_node_side(ebone)              

            # Update constraint targets
            bpy.ops.object.mode_set(mode='POSE')
            for b in get_selected_pose_bones():
                try:
                    b.constraints[0].subtarget = b.constraints[0].subtarget.replace(side, '_dupli_' + dupli_id + side[-2:])
                except:
                    pass

            edit_rig(rig)

        # Create dupli properties on the c_pos bones
        _s = side
        if side == None:
            _s = ".x"

        get_pose_bone('c_pos')[limb + ' ' + dupli_id + _s] = True
        
        # end duplicate_rig()
        
       
    # get the bone side
    side = get_bone_side(selected_bones_names[0])

    # limb type
    limb_type = ''
    if "arp_spline" in sel_bone.keys():
        limb_type = "spline_ik"
    elif "arp_bbones" in sel_bone.keys():
        limb_type = "bbones"
    else:
        limb_type = selected_bones_names[0]
    
    dupli_id, found_base = get_next_dupli_id(get_opposite_side(side) if dupli_mirror else side, limb_type, mirror=dupli_mirror)    
    
    print("new dupli_id:", dupli_id, found_base)

    # Duplicate the selected limb
    limb_to_dupli = None
    # Get the selected limb type to duplicate    
    
    # spline ik
    if selected_bones_names[0].startswith("spline_") or "arp_spline" in sel_bone.keys():
        if "arp_spline" in sel_bone.keys():
            spline_name = sel_bone["arp_spline"]
            limb_to_dupli = "spline_ik"

    # bbones
    if limb_to_dupli == None:
        if selected_bones_names[0].startswith("bbones_") or "arp_bbones" in sel_bone.keys():
            if "arp_bbones" in sel_bone.keys():
                bbones_name = sel_bone["arp_bbones"]
                limb_to_dupli = "bbones"
                
    # arm
    for i in arm_ref_list:
        if selected_bones_names[0].startswith(i):
            limb_to_dupli = "arm"
            break

    # leg
    if limb_to_dupli == None:
        for i in leg_ref_bones_list:
            if selected_bones_names[0] in i + side:
                limb_to_dupli = "leg"
                break

    # head
    if limb_to_dupli == None:
        _facial_ref = ard.facial_ref + ["head_ref.x", "neck_ref.x"]
        for i in _facial_ref:
            if selected_bones_names[0].startswith(i[:-2]):
                limb_to_dupli = "head"
                break

    # ear
    if limb_to_dupli == None:
        bone_name_split = selected_bones_names[0].split('_')
        if selected_bones_names[0].startswith("ear_") and len(bone_name_split) >= 3:
            if bone_name_split[2].startswith("ref"):
                limb_to_dupli = "ear"

    # tail 
    if limb_to_dupli == None:
        if ("master_at_root" in sel_bone.keys() or sel_bone.name.startswith("tail_")) and "_ref" in sel_bone.name:
            limb_to_dupli = "tail"    
    
    if limb_to_dupli:
        if limb_to_dupli == 'head':
            symmetrical = True
            
        duplicate_rig(limb_to_dupli, side, dupli_id, found_base)
        duplicate_ref(limb_to_dupli, side, dupli_id, found_base)

    # mode switch necessary to avoid crash currently
    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.mode_set(mode='EDIT')

    if rig_add:    
        rig_add.select_set(state=False)
        hide_object(rig_add)

    # Restore layers
    restore_armature_layers(layers_select)

    # restore picker
    restore_proxy_picker(proxy_picker_state)

    # restore x mirror
    if symmetrical == False:# do not restore for symmetrical limbs (head), no mirrort
        rig.data.use_mirror_x = mirror_x_state


def get_selected_pair(obj_id):
    obj_1 = bpy.context.view_layer.objects.active
    obj_2 = None

    if bpy.context.selected_objects[0] == obj_1:
        obj_2 = bpy.context.selected_objects[1]
    else:
        obj_2 = bpy.context.selected_objects[0]

    if obj_id == 1:
        return obj_1
    if obj_id == 2:
        return obj_2


def get_rig_add(_rig):
    rig_add_obj = None
    rig_parent = _rig.parent

    if rig_parent != None:
        for obj_child in rig_parent.children:
            if 'rig_add' in obj_child.name and not 'prop' in obj_child.name:
                rig_add_obj = obj_child
                break

        if 'obj_child' in locals():
            del obj_child

    return rig_add_obj


def is_facial_bone(bone_name):
    for bfacial in ard.facial_deform:
        if bfacial in bone_name:
            return True


def save_ebone_data(self):
    if bpy.context.active_object.override_library or is_proxy(bpy.context.active_object):
        # cannot enter edit mode for linked rig, do not save bone data 
        # anyway the original rig is restored at the end of the process
        return
        
    self.ebones_dict = {}
    bpy.ops.object.mode_set(mode='EDIT')
    
    for ebone in bpy.context.active_object.data.edit_bones:
        self.ebones_dict[ebone.name] = ebone.head.copy(), ebone.tail.copy(), ebone.roll, ebone.use_deform


def restore_rig_data(self):
    
    # restore bones data    
    for bone_name in self.ebones_dict:
        ebone = get_edit_bone(bone_name)
        ebone.head, ebone.tail, ebone.roll, ebone.use_deform = self.ebones_dict[bone_name]        
        
    # delete helper skinning bones
    for eb in bpy.context.active_object.data.edit_bones:
        found = False
        for bone_name in self.ebones_dict:
            if eb.name == bone_name:
                found = True
        if not found:
            delete_edit_bone(eb)


def add_armature_modifiers(self, remove_current=False):
    scn = bpy.context.scene
    cur_obj = bpy.context.active_object
    
    if remove_current:
        for mod in cur_obj.modifiers:
            if mod.type == "ARMATURE":
                cur_obj.modifiers.remove(mod)
    
    if self.bind_rig_add:
        if cur_obj.modifiers.get("rig_add") == None:
            new_mod = cur_obj.modifiers.new("rig_add", "ARMATURE")
            new_mod.object = self.rig_add
            new_mod.name = "rig_add"

    if self.bind_rig:
        if cur_obj.modifiers.get("rig") == None:
            new_mod = cur_obj.modifiers.new("rig", "ARMATURE")
            new_mod.object = self.rig
            new_mod.use_deform_preserve_volume = scn.arp_bind_preserve
            new_mod.name = "rig"
            new_mod.show_in_editmode = True
            new_mod.show_on_cage = True

    # Order modifier stack
    
    i_test = 0# for safety, some modifiers such as Multires can't be moved
    while cur_obj.modifiers[0] != cur_obj.modifiers["rig"] and i_test < 50:
        i_test += 1
        try:
            bpy.ops.object.modifier_move_up(modifier="rig")
        except:
            pass

    if self.bind_rig_add:
        i_test = 0
        while cur_obj.modifiers[0] != cur_obj.modifiers["rig_add"] and i_test < 50:
            i_test += 1
            try:
                bpy.ops.object.modifier_move_up(modifier="rig_add")
            except:
                pass

    # put mirror at first
    for m in bpy.context.active_object.modifiers:
        if m.type == 'MIRROR':
            i_test = 0
            while cur_obj.modifiers[0] != cur_obj.modifiers[m.name] and i_test < 50:
                i_test += 1
                try:                
                    bpy.ops.object.modifier_move_up(modifier=m.name)
                except:
                    pass

   
    
def restore_mask_modifiers(dict):
    for oname in dict:
        _o = get_object(oname)
        for modname in dict[oname]:
            _o.modifiers.get(modname).show_viewport = True


def disable_mask_modifiers(objects):
    disabled_mod_dict = {}
    for _o in objects:
        disabled_mod_list = []
        for mod in _o.modifiers:
            if mod.type == "MASK" and mod.show_viewport:
                mod.show_viewport = False
                disabled_mod_list.append(mod.name)

        disabled_mod_dict[_o.name] = disabled_mod_list

    return disabled_mod_dict


def bind_skin_eyeballs(obj, self):
    scn = bpy.context.scene

    def remove_vgroups(obj):
        if len(obj.vertex_groups):
            for vgroup in obj.vertex_groups:
                if vgroup.lock_weight == False:
                    obj.vertex_groups.remove(vgroup)


    def create_vgroup(obj, group_name):
        remove_vgroups(obj)

        # create and assign eye vgroup
        obj.vertex_groups.new(name=group_name)

        for v in obj.data.vertices:
            obj.vertex_groups[group_name].add([v.index], 1.0, 'ADD')

    # automatic eyeballs skinning, if defined
    has_skinned_eyeball = False
    if scn.arp_eyeball_type == "SEPARATE":
        if obj.name == scn.arp_eyeball_name:
            print("Skinning left eyeball...")
            remove_vgroups(obj)
            print(len(obj.vertex_groups))
            create_vgroup(obj, "c_eye.l")
            has_skinned_eyeball = True

        elif obj.name == scn.arp_eyeball_name_right:
            print("Skinning right eyeball...")
            remove_vgroups(obj)
            print(len(obj.vertex_groups))
            create_vgroup(obj, "c_eye.r")
            has_skinned_eyeball = True

    elif scn.arp_eyeball_type == "SINGLE":
        if obj.name == scn.arp_eyeball_name:
            print("Skinning eyeballs...")

            remove_vgroups(obj)

            # create vgroups
            for vg_name in ["c_eye.l", "c_eye.r"]:
                obj.vertex_groups.new(name=vg_name)

            vi_list = [v.index for v in obj.data.vertices]

            set_active_object(obj.name)
            scn.tool_settings.transform_pivot_point = 'BOUNDING_BOX_CENTER'

            while len(vi_list) > 0:
                # select the first vert of the remaining vert list and select the linked piece
                bpy.ops.object.mode_set(mode="EDIT")
                bpy.ops.mesh.select_all(action='DESELECT')
                bpy.ops.object.mode_set(mode="OBJECT")
                obj.data.vertices[vi_list[0]].select = True
                bpy.ops.object.mode_set(mode="EDIT")
                bpy.ops.mesh.select_linked(delimit=set())                
                bpy.ops.view3d.snap_cursor_to_selected()
                # get the center
                center_loc = scn.cursor.location.copy()
                bpy.ops.object.mode_set(mode="OBJECT")

                # set vertex group side depending on center X pos
                for v in obj.data.vertices:
                    if v.select:                        
                        if center_loc[0] > 0:
                            obj.vertex_groups["c_eye.l"].add([v.index], 1.0, 'ADD')
                        else:
                            obj.vertex_groups["c_eye.r"].add([v.index], 1.0, 'ADD')    
                          
                # remove evaluated vertices from the list
                for v in obj.data.vertices:
                    if v.select:
                        vi_list.remove(v.index)

            has_skinned_eyeball = True
        
    if has_skinned_eyeball:
        # add armature modifiers
        set_active_object(obj.name)
        add_armature_modifiers(self)
        return "continue"
    
    
    return "skinned"


def bind_set_collec(self):
    for obj_name in self.obj_to_skin_save:
        obj = get_object(obj_name)

        if obj == None:
            continue

        if len(self.rig.users_collection) > 0:
            rig_collecs = [col.name for col in self.rig.users_collection]
            for scene_collec in bpy.data.collections:
                for child in scene_collec.children:
                    if child.name in rig_collecs:
                        name_split = child.name.split('_')
                        if len(name_split) == 2:
                            if name_split[1] == "rig":
                                try:
                                    scene_collec.objects.link(obj)
                                    break
                                except:
                                    pass


def bind_improve_weights(_obj, self):
    scn = bpy.context.scene

    # store class bools as local var to minimize overhead
    facial_enabled = is_facial_enabled(self.rig)
    self_enable_head_refine = self.enable_head_refine
    self_improve_hips_skinning = self.improve_hips_skinning
    self_improve_heels_skinning = self.improve_heels_skinning
    scn_arp_bind_chin = scn.arp_bind_chin
    obj_mat = _obj.matrix_world.copy()
    rig_mat = self.rig.matrix_world.copy()

    if facial_enabled or self_improve_hips_skinning or self_improve_heels_skinning:
        print("  Improve skinning...")
        print("  hips:", self_improve_hips_skinning)
        print("  heels:", self_improve_heels_skinning)

        legs_hips = {'c_thigh_b': ['root.x']}
        foot_heel = {'foot_heel_h': ['foot']}
        
        # generates eyelids dict
        eyelid_transf = {}
        for vg in _obj.vertex_groups:
            if vg.name.startswith('eyelid_h_top') or vg.name.startswith('eyelid_h_bot'):
                idx = ard.get_eyelid_idx(vg.name)
                tar_bone = ''
                if idx == 0:# first, corner
                    tar_bone = 'c_eyelid_corner_01'
                elif idx == self.eyelids_count+1:# last, corner
                    tar_bone = 'c_eyelid_corner_02'
                else:
                    str_idx = '%02d' % idx
                    lvl = 'top_' if 'top' in vg.name else 'bot_'
                    tar_bone = 'c_eyelid_'+lvl+str_idx
                    
                eyelid_transf[get_bone_base_name(vg.name)] = [tar_bone]
        
        # transfer weights with operators (faster)
        if facial_enabled:
            
            # eyelids
            transfer_weight_mod(object=_obj, dict=eyelid_transf)
            # eye offset
            transfer_weight_prefix_mod(object=_obj, prefix="eyeoffset_temp_", tar_grp_base_name="c_eye_offset")

        if self_improve_hips_skinning:
            # hips
            transfer_weight_mod(object=_obj, dict=legs_hips)

        if self_improve_heels_skinning:
            # heels
            transfer_weight_mod(object=_obj, dict=foot_heel)

        print("  remove temp vgroups...")

        # Delete helpers vertex groups        
        if facial_enabled:
            for vgroup in _obj.vertex_groups:
                if "eyelid_h_" in vgroup.name or vgroup.name.startswith("eyeoffset_temp_"):
                    _obj.vertex_groups.remove(vgroup)
        
        if self_improve_hips_skinning:
            for leg_side in limb_sides.leg_sides:
                vgroup = _obj.vertex_groups.get("c_thigh_b"+leg_side)
                if vgroup:
                    _obj.vertex_groups.remove(vgroup)

        if self_improve_heels_skinning:
            for leg_side in limb_sides.leg_sides:
                vgroup = _obj.vertex_groups.get("foot_heel_h"+leg_side)
                if vgroup:
                    _obj.vertex_groups.remove(vgroup)


    # Improve head weights. for bipeds only.
    
    if self_enable_head_refine:
        head_sides = limb_sides.head_sides
        
        if not facial_enabled:
            
            for head_side in head_sides:
                print('  improve jaw weights...', head_side)
                # transfer virtual jaw to head weights
                tar_grp = 'c_skull_01' if _obj.vertex_groups.get('c_skull_01'+head_side) else 'head' 
                transfer_weight_mod(object=_obj, dict={'jaw_h_helper' : [tar_grp]})
                
                # clean buggy head weights
                if _obj.vertex_groups.get('head'+head_side):                
                    print('  cleaning head weights...', head_side)
                    remove_other_parts = ["thumb", "hand", "index", "middle", "ring", "pinky", "arm_", "forearm", "shoulder_bend"]

                    for vert in _obj.data.vertices:
                        is_in_head_group = False                                           

                        if len(vert.groups):
                            for grp in vert.groups:
                                vg_id = grp.group
                                if vg_id > len(_obj.vertex_groups)-1 or vg_id < 0:# unknown bug, vertex group ID is invalid, skip for now
                                    continue

                                cur_vgroup = _obj.vertex_groups[vg_id]
                                group_name = cur_vgroup.name

                                # check if is in head
                                if group_name == 'head'+head_side:
                                    if grp.weight > 0.1:
                                        is_in_head_group = True
                                        
                                for part in remove_other_parts:
                                    if part in group_name and is_in_head_group:
                                        cur_vgroup.add([vert.index], 0.00, 'REPLACE')
                
            # Delete helpers vertex groups        
            for vgroup in _obj.vertex_groups:
                if vgroup.name.startswith('jaw_h_helper'):
                    _obj.vertex_groups.remove(vgroup)
                    
        
        # smooth neck
        for head_side in head_sides:
            if _obj.vertex_groups.get('neck'+head_side):
                smooth_neck = True

                if smooth_neck:
                    print('  smoothing neck weights...'+ head_side)
                    
                    bpy.ops.object.mode_set(mode='WEIGHT_PAINT')            
                    _obj.vertex_groups.active_index = _obj.vertex_groups['neck'+head_side].index
                    bpy.context.active_object.data.use_paint_mask_vertex = True
                    bpy.ops.paint.vert_select_all(action='SELECT')            
                    bpy.ops.object.vertex_group_smooth(group_select_mode='ACTIVE', factor=0.5, repeat=4, expand=0.0)
                    bpy.ops.paint.vert_select_all(action='DESELECT')
                    bpy.context.active_object.data.use_paint_mask_vertex = False
            

    if self.smooth_twists:
        print('  smoothing twists weights...')

        transfer_twists = {'c_arm_twist_offset': ['arm_stretch'], 'forearm_stretch': ['forearm_twist'],
                           'thigh_twist': ['thigh_stretch'], 'leg_stretch': ['leg_twist']}
        copy_weights = {'arm_stretch': ['c_arm_twist_offset'], 'forearm_twist': ['forearm_stretch'],
                        'thigh_stretch': ['thigh_twist'], 'leg_twist': ['leg_stretch']}

        # if secondary controllers set Twist, must operate on the controller bones
        if self.rig.arp_secondary_type == "TWIST_BASED":
            transfer_twists = {'c_arm_twist': ['c_arm_stretch'], 'c_forearm_stretch': ['c_forearm_twist'],
                               'c_thigh_twist': ['c_thigh_stretch'], 'c_leg_stretch': ['c_leg_twist']}
            copy_weights = {'c_arm_stretch': ['c_arm_twist'], 'c_forearm_twist': ['c_forearm_stretch'],
                            'c_thigh_stretch': ['c_thigh_twist'], 'c_leg_twist': ['c_leg_stretch']}

        # merge the stretch and twist groups together
        # transfer weights with operators (faster)
        transfer_weight_mod(object=_obj, dict=transfer_twists)
        transfer_weight_mod(object=_obj, dict=copy_weights, replace=True)


        # apply a gradient decay based on the bone head/tail position
        first_bones_dict = {}# store local vars to minimize overhead in the loop
        second_bones_dict = {}

        for vert in _obj.data.vertices:
            if len(vert.groups):
                for grp in vert.groups:

                    grp_idx = grp.group
                    if grp_idx > len(_obj.vertex_groups)-1 or grp_idx < 0:# unknown error, grp idx is invalid
                        continue

                    current_grp_name = _obj.vertex_groups[grp_idx].name
                    side = get_bone_side(current_grp_name)

                    for bone_group in transfer_twists:
                        first_bone = bone_group
                        second_bone = transfer_twists[bone_group][0]

                        if current_grp_name == first_bone + side or current_grp_name == second_bone + side:

                            # get the vertice position projected on the arm bone line
                            if not first_bone+side in first_bones_dict:
                                first_bones_dict[first_bone+side] = rig_mat @ self.rig.pose.bones[first_bone+side].head.copy()

                            if not second_bone+side in second_bones_dict:
                                second_bones_dict[second_bone+side] = rig_mat @ self.rig.pose.bones[second_bone+side].tail.copy()

                            bone_head = first_bones_dict[first_bone+side]
                            bone_tail = second_bones_dict[second_bone+side]

                            point = obj_mat @ vert.co
                            pos = project_point_onto_line(bone_head, bone_tail, point)
                            # get the normalized distance as decay factor
                            distance = (bone_head - pos).magnitude / (bone_head - bone_tail).magnitude

                            if first_bone in current_grp_name:
                                _obj.vertex_groups[first_bone + side].add([vert.index], grp.weight * (1 - distance), 'REPLACE')

                            if second_bone in current_grp_name:
                                # if the projected point is below the bone's head, set distance to 0
                                fac = get_point_projection_onto_line_factor(bone_head, bone_tail, point)
                                if fac[0] < 0:
                                    distance = 0

                                _obj.vertex_groups[second_bone + side].add([vert.index], grp.weight * distance, 'REPLACE')

                            break

    if facial_enabled:
        print('  smoothing eyelids weights...')

        bpy.context.active_object.data.use_paint_mask_vertex = True
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.select_all(action='DESELECT')

        vgroups = _obj.vertex_groups

        eyelids_smooth = ["c_eyelid_bot_01", "c_eyelid_bot_02", "c_eyelid_bot_03", "c_eyelid_top_01", "c_eyelid_top_02", "c_eyelid_top_03"]
        
        for side in self.sides:
            for bgroup in eyelids_smooth:
                if _obj.vertex_groups.get(bgroup + side):
                    # select verts
                    vgroups.active_index = vgroups[bgroup + side].index
                    bpy.ops.object.vertex_group_select()

            # smooth weights
            bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
            bpy.ops.object.vertex_group_smooth(group_select_mode='ALL', factor=0.5, repeat=1, expand=0.5)
        
        bpy.ops.paint.vert_select_all(action='DESELECT')
            
        bpy.context.active_object.data.use_paint_mask_vertex = False

    bpy.ops.object.mode_set(mode='OBJECT')



def is_eyeball_objects(obj, scn):
    if scn.arp_eyeball_type == "SINGLE":
        if obj.name == scn.arp_eyeball_name:
            return True
    elif scn.arp_eyeball_type == "SEPARATE":
        if obj.name == scn.arp_eyeball_name or obj.name == scn.arp_eyeball_name_right:
            return True
    return False


def bind_prepare(self):
    scn = bpy.context.scene
    
    self.obj_to_skin = [obj.name for obj in bpy.context.selected_objects if obj.type == "MESH" and not is_object_hidden(obj)]
    
    # if linked armature, a local copy is necessary
    target_proxy_name = None   
    cur_rig_name = bpy.context.view_layer.objects.active.name
    cur_rig = get_object(bpy.context.view_layer.objects.active.name)
    
    if is_proxy(cur_rig):
        target_proxy_name = cur_rig.proxy.name
        print("  The target armature is a proxy. Real name = ", target_proxy_name)


    if target_proxy_name or cur_rig.override_library:
        #   select
        bpy.ops.object.mode_set(mode='OBJECT')
        bpy.ops.object.select_all(action='DESELECT')
        set_active_object(cur_rig_name)
        self.rig_original = cur_rig
        
        local_armature_name = cur_rig_name+"_local"
        
        #   duplicate        
        if get_object(local_armature_name) == None:
            duplicate_object()
            bpy.context.active_object.name = local_armature_name
          
        bpy.ops.object.select_all(action='DESELECT')
        set_active_object(local_armature_name)
        proxy_armature = get_object(local_armature_name)
        
        #   localize
        proxy_armature.data = proxy_armature.data.copy()        
      
        if cur_rig.override_library:
            print("  Localize override...")           
            bpy.ops.object.make_local(type='SELECT_OBDATA')#(type='SELECT_OBJECT') 
            
        self.rig = proxy_armature
        self.rig_add = get_rig_add(self.rig_original)
        
    else:    
        self.rig = get_object(bpy.context.view_layer.objects.active.name)        
        self.rig_add = get_rig_add(self.rig)
    
    self.rig.data.pose_position = 'REST'
    self.smooth_twists = scn.arp_bind_improve_twists
    
    # if VHDS engine, and selected verts only, invert vert selection (preserve selected is opposite of bind only selected)
    if "modal_state" in dir(self):
        for objname in self.obj_to_skin:
            o = get_object(objname)
            for v in o.data.vertices:
                v.select = not v.select
    
    bpy.ops.object.mode_set(mode='EDIT')

    selected_bones = [b.name for b in self.rig.data.bones if b.select]

    # disable X mirror topology for all meshes, cause issues
    for objname in self.obj_to_skin:
        o = get_object(objname)
        o.data.use_mirror_topology = False

    if self.rig_add:
        unhide_object(self.rig_add)

    # define which armature to bind to
    self.bind_rig = True
    self.bind_rig_add = self.rig.arp_secondary_type == 'ADDITIVE'

    # Disable eyeballs auto skinning if the eyeball objects can't be found
    if not is_facial_enabled(self.rig):
        self.auto_eyeballs = False
    if get_object(scn.arp_eyeball_name) == None:
        self.auto_eyeballs = False
    if scn.arp_eyeball_type == "SEPARATE":
        if get_object(scn.arp_eyeball_name_right) == None:
            self.auto_eyeballs = False

    if self.auto_eyeballs == False:
        print("Eyeballs object(s) not found, eyeball skinning skipped")

    if self.rig.arp_secondary_type == "BENDY_BONES" or is_object_arp(bpy.context.active_object) == False:
        self.smooth_twists = False

    # if there are multiple twist bones, do not smooth twist weights (with gradient decays)
    set_active_object(self.rig.name)

    bpy.ops.object.mode_set(mode='EDIT')

    for side in limb_sides.arm_sides:
        b = get_edit_bone('arm_ref'+side)
        if len(b.keys()) > 0:
            if 'twist_bones_amount' in b.keys():
                if b['twist_bones_amount'] > 1:
                    self.smooth_twists = False

    for side in limb_sides.leg_sides:
        b = get_edit_bone('thigh_ref' + side)
        if len(b.keys()) > 0:
            if 'twist_bones_amount' in b.keys():
                if b['twist_bones_amount'] > 1:
                    self.smooth_twists = False

    # backward-compatibility: make sure to set the 'picker' bone to no deform
    picker = get_edit_bone('Picker')
    if picker:
        picker.use_deform = False


    # is the chin marker saved?
    self.chin_loc = None
    if len(self.rig.data.keys()):
        if "arp_chin_loc" in self.rig.data.keys():
            self.chin_loc = self.rig.data["arp_chin_loc"]


    
    # Improve facial skinning
    self.improve_hips_skinning = scn.arp_bind_improve_hips
    self.improve_heels_skinning = scn.arp_bind_improve_heels
    self.enable_head_refine = scn.arp_bind_chin
    
    
    # get facial duplis id
    head_sides = limb_sides.head_sides
    facial_duplis_id = [h[:-2] for h in head_sides]
        
    if is_facial_enabled(self.rig):
        print("Setup virtual eyelids...")
        
        # Eyelid helper bones loop: bone_name: [(head pos 1, head pos 2, interpolation factor), (tail pos 1, tail pos 2, interpolation factor)]
        for id_dupli in facial_duplis_id:
            for side in self.sides:
                eyel_var_cont = ard.get_variable_eyelids(id_dupli+'.x', type='CONTROLLER', eye_sides=[side], no_side=True)
                
                if len(eyel_var_cont) == 0:# no eyelids for this side                  
                    continue
                    
                # get last variable eyelid index
                last_eyel_idx = 0
                for eyel_name in eyel_var_cont:
                    i = ard.get_eyelid_idx(eyel_name)                        
                    if i > last_eyel_idx:
                        last_eyel_idx = i
                self.eyelids_count = last_eyel_idx# for now, only support all eyelids with same total counts. Todo, multiple variable counts (rare case)
                
                # build chain
                eyelid_helper_bones = {}                    
                
                for eyel_name in eyel_var_cont:                   
                    i = ard.get_eyelid_idx(eyel_name)                    
                    pre_i = i-1
                    nxt_i = i+1
                    s_i = '%02d' % i
                    s_pre_i = '%02d' % pre_i
                    s_nxt_i = '%02d' % nxt_i  
                    
                    lvl = 'top_'
                    if 'bot_' in eyel_name:
                        lvl = 'bot_'
                    
                    bone_pre = 'c_eyelid_'+lvl+s_pre_i                       
                    h_name = 'eyelid_h_'+lvl+s_i
                    h_name_prev = 'eyelid_h_'+lvl+s_pre_i+'_01'
                    fac = 0.5
                    if i == 1:
                        bone_pre = 'c_eyelid_corner_01'
                        h_name_prev = 'eyelid_h_'+lvl+s_pre_i
                        fac = 0.25
                    elif i == last_eyel_idx:                     
                        eyelid_helper_bones['eyelid_h_'+lvl+s_i+'_01'] = [(eyel_name, eyel_name, 0.0), (eyel_name, 'c_eyelid_corner_02', 0.75)]                      
                        eyelid_helper_bones['eyelid_h_'+lvl+s_nxt_i] = [(eyel_name, 'c_eyelid_corner_02', 0.75), ('c_eyelid_corner_02', 'c_eyelid_corner_02', 0.0)]
                    
                    eyelid_helper_bones[h_name_prev] = [(bone_pre, bone_pre, 0.0), (bone_pre, eyel_name, fac)]
                    eyelid_helper_bones[h_name] = [(bone_pre, eyel_name, fac), (eyel_name, eyel_name, 0.0)]

                for bone_name in eyelid_helper_bones:    
                    # head
                    head1_name = eyelid_helper_bones[bone_name][0][0]+id_dupli+side
                    head2_name = eyelid_helper_bones[bone_name][0][1]+id_dupli+side
                    head_pos1 = get_edit_bone(head1_name)
                    head_pos2 = get_edit_bone(head2_name)
                    fac_head = eyelid_helper_bones[bone_name][0][2]
                    if head_pos1 == None or head_pos2 == None:
                        print("Could not set eyelid helper bone, bones not found:", head1_name, head2_name)
                        continue

                    head_pos = head_pos1.tail + (head_pos2.tail - head_pos1.tail) * fac_head

                    # tail
                    tail_pos1 = get_edit_bone(eyelid_helper_bones[bone_name][1][0]+id_dupli+side)
                    tail_pos2 = get_edit_bone(eyelid_helper_bones[bone_name][1][1]+id_dupli+side)
                    fac_tail = eyelid_helper_bones[bone_name][1][2]
                    if tail_pos1 == None or tail_pos2 == None:
                        print("Could not set eyelid helper bone, bones not found:", bone_name)
                        continue

                    tail_pos = tail_pos1.tail + (tail_pos2.tail - tail_pos1.tail) * fac_tail

                    # create bone
                    new_bone = create_edit_bone(bone_name+id_dupli+side, deform=True)
                    new_bone.head, new_bone.tail = head_pos, tail_pos

                    # disable base bone deform
                    head_pos1.use_deform = head_pos2.use_deform = tail_pos1.use_deform = tail_pos2.use_deform = False
                    # select (Selected Bones Only support)
                    if head_pos1.select or head_pos2.select or tail_pos1.select or tail_pos2.select:                        
                        selected_bones.append(new_bone.name)
        
        print("Setup virtual lips...")                              
        # temporarily set the lips bones in circle for a better skinning
        lips_var_cont = ard.get_variable_lips('.x', type='CONTROLLER')
        
        lips_list = ["c_lips_top.x", "c_lips_top.l", "c_lips_smile.l",
                     "c_lips_bot.l", "c_lips_bot.x", "c_lips_top.r", 
                     "c_lips_smile.r", "c_lips_bot.r"] + lips_var_cont

        lips_bones = {"c_lips_top.x": ["c_lips_top.r", "c_lips_top.l"],
                      "c_lips_top.l": ["c_lips_top.x", "c_lips_top_01.l"],                      
                      "c_lips_bot.l": ["c_lips_bot_01.l", "c_lips_bot.x"],
                      "c_lips_bot.x": ["c_lips_bot.l", "c_lips_bot.r"],
                      "c_lips_top.r": ["c_lips_top.x", "c_lips_top_01.r"],                      
                      "c_lips_bot.r": ["c_lips_bot_01.r", "c_lips_bot.x"]}                      
        
        if len(lips_var_cont) == 0:
            for side in ['.l', '.r']:
                lips_bones['c_lips_top'+side] = ["c_lips_top.x", "c_lips_smile"+side]
                lips_bones['c_lips_bot'+side] = ["c_lips_bot.x", "c_lips_smile"+side]
                
        
        # get last variable lip index
        last_lip_idx = 0
        for lip_name in lips_var_cont:
            i = lip_name.split('_')[3].split('.')[0]
            i = int(i)
            if i > last_lip_idx:
                last_lip_idx = i
        
        # add variable lips in dict
        for lip_name in lips_var_cont: 
            str_idx = ard.get_lip_idx(lip_name)#lip_name.split('_')[3].split('.')[0]
            idx = int(str_idx)
            str_nxt_idx = '%02d' % (idx+1)
            str_pre_idx = '%02d' % (idx-1)
            lvl = lip_name.split('_')[2]
            side = '.'+lip_name.split('.')[1]
            prev = None
            next = None
            
            if idx == 1:# first lip
                prev = 'c_lips_'+lvl+side
            if idx == last_lip_idx:# last lip
                next = 'c_lips_smile'+side
                if prev == None:# not the first one
                    prev = 'c_lips_'+lvl+'_'+str_pre_idx+side
            if idx != last_lip_idx:# inter
                next = 'c_lips_'+lvl+'_'+str_nxt_idx+side
                if idx != 1:
                    prev = 'c_lips_'+lvl+'_'+str_pre_idx+side
            lips_bones[lip_name] = [prev, next]
        
        initial_lips = {}

        # store in dict
        for lip_name in lips_list:
            for dupli_id in facial_duplis_id:
                bname = lip_name.replace(lip_name[-2:], dupli_id) + lip_name[-2:]# retarget bone side
                eb = get_edit_bone(bname)
                if eb:
                    initial_lips[bname] = eb.head.copy(), eb.tail.copy(), eb.roll

        for bone in lips_bones:
            for dupli_id in facial_duplis_id:
                bname = bone.replace(bone[-2:], dupli_id) + bone[-2:]
                if initial_lips.get(bname):
                    s1 = initial_lips[bname][0]
                    s2 = initial_lips[lips_bones[bone][0][:-2] + dupli_id + lips_bones[bone][0][-2:]][0]
                    s3 = initial_lips[lips_bones[bone][1][:-2] + dupli_id + lips_bones[bone][1][-2:]][0]

                    if get_edit_bone(bname):
                        get_edit_bone(bname).head = (s1 + s2) * 0.5
                        get_edit_bone(bname).tail = (s1 + s3) * 0.5
        
        # disable c_eye deform if auto eyeball skinning
        if self.auto_eyeballs:
            for side in self.sides:
                get_edit_bone("c_eye"+side).use_deform = False

        # Set eyelids borders bones
        def create_looping_bones(data, side):
            for i, v_data in enumerate(data):# [[vertex_index, (vertex cos)], [2, (0.2,0.5,0.3)], ...]
                vi, vcos = v_data[0], v_data[1]
                next_index = i+1
                # to loop, the last index is the first
                if i == len(data)-1:
                    next_index = 0
                next_v_data = data[next_index]
                next_vi, next_vcos = next_v_data[0], next_v_data[1]
                new_bone = bpy.context.active_object.data.edit_bones.new("eyeoffset_temp_" + str(vi) + side)
                new_bone.head = self.rig.matrix_world.inverted() @ vcos
                new_bone.tail = self.rig.matrix_world.inverted() @ next_vcos

        if eyelids_borders_data.left_borders:
            create_looping_bones(eyelids_borders_data.left_borders, ".l")
        if eyelids_borders_data.right_borders:
            create_looping_bones(eyelids_borders_data.right_borders, ".r")

        
    if not is_facial_enabled(self.rig) and self.enable_head_refine:
        print('Setup virtual jaw...')
        
        for id_dupli in facial_duplis_id:
            head_side = id_dupli+'.x'
            head_eb = get_edit_bone(ard.heads_dict['deform'][:-2]+head_side)
            
            # create helper jaw bone
            jaw_h_eb = create_edit_bone('jaw_h_helper'+head_side, deform=True)
            jaw_h_eb.head = head_eb.head.copy()
            
            has_set_chin_loc = False
            if id_dupli == '':# main head, possible smart detection with chin loc
                if self.chin_loc:
                    if 'arp_chin_pos_vec' in self.rig.data.keys():# backward-compatibility
                        jaw_h_eb.tail = self.rig.data['arp_chin_pos_vec']
                        print('  set real chin pos')
                    else:# old method, no 3d pos. Only use Z pos and and approximate length
                        jaw_h_eb.tail = jaw_h_eb.head + ((head_eb.tail - head_eb.head).magnitude * 0.5 * head_eb.z_axis.normalized())
                        jaw_h_eb.tail[2] = self.chin_loc                    
                        print('  set 2d chin pos')
                    has_set_chin_loc = True
            
            if has_set_chin_loc == False:
                print('  set approximate chin pos')
                jaw_h_eb.tail = head_eb.head + (head_eb.z_axis.normalized() * (head_eb.tail - head_eb.head).magnitude * 0.5)

    
    bpy.ops.object.mode_set(mode='EDIT')

    if self.improve_hips_skinning:
        # improve hips skinning by enabling c_thigh_b bones deformation, then transfer these weights to the root bone
        for leg_side in limb_sides.leg_sides:
            c_thigh_b = get_edit_bone("c_thigh_b" + leg_side)
            if c_thigh_b == None:
                continue
            # don't improve hips skinning if this is a 3 bones leg type
            if c_thigh_b.use_deform:
                self.improve_hips_skinning = False
                break
            else:
                if self.improve_hips_skinning:
                    c_thigh_b.use_deform = True

    if self.improve_heels_skinning:
        # improve heels skinning
        for leg_side in limb_sides.leg_sides:
            foot = get_edit_bone('foot'+leg_side)
            heel_ref = get_edit_bone('foot_heel_ref'+leg_side)

            if foot == None or heel_ref == None:
                continue

            foot_heel_h_name = "foot_heel_h"+leg_side
            foot_heel_h = self.rig.data.edit_bones.new(foot_heel_h_name)
            foot_heel_h.head = foot.head.copy()
            foot_heel_h.tail = heel_ref.head.copy()

            
    bpy.ops.object.mode_set(mode='POSE')
    
    # selected bones only
    if scn.arp_bind_selected_bones:
        for pb in self.rig.pose.bones:
            if not pb.name in selected_bones:
                pb.bone.use_deform = False

    bpy.ops.object.mode_set(mode='OBJECT')

    self.obj_to_skin_save = [i for i in self.obj_to_skin]# save the original list, to access it later

def bind_finalize(self):
    print("Finalize...")
    
    if self.rig_add:
        hide_object(self.rig_add)
    
     # delete temp localized rig
    if self.rig_original:
        delete_object(self.rig)
        self.rig = self.rig_original
        
        # assign the original rig as target in armature modifiers
        for obj_name in self.obj_to_skin_save:
            o = get_object(obj_name)
            for mod in o.modifiers:
                if mod.type == 'ARMATURE' and mod.object == None:
                    mod.object = self.rig_original
    
    bpy.ops.object.select_all(action='DESELECT')
    
    set_active_object(self.rig.name)
    self.rig.data.pose_position = 'POSE'


def bind_parent(self):
    for objname in self.obj_to_skin_save:
        bpy.ops.object.select_all(action='DESELECT')
        set_active_object(objname)
        set_active_object(self.rig.name)
        bpy.ops.object.parent_set(type='OBJECT', keep_transform=True)


def restore_scale_fix(self):
    while len(self.scale_fixed_objects):
        objname = self.scale_fixed_objects[0]
        ob = get_object(objname)

        if ob != self.rig and ob != self.rig_add:
            # unparent meshes
            ob_mat = ob.matrix_world.copy()
            ob.parent = None
            ob.matrix_world = ob_mat

        # revert scale
        ob.location *= 1/self.scale_ratio
        ob.scale *= 1/self.scale_ratio

        self.scale_fixed_objects.pop(0)


def _bind_to_rig(self, context):
    print("Binding...")
    time_start = time.time()
    scn = bpy.context.scene
    
    bind_prepare(self)
    
    weights_dict = {}
    non_selected_verts_dict = {}
    
    # scale
    if scn.arp_bind_scale_fix:
        # scale meshes
        for objname in self.obj_to_skin + [self.rig.name]:
            ob = get_object(objname)
            ob.location *= self.scale_ratio
            ob.scale *= self.scale_ratio
            self.scale_fixed_objects.append(ob.name)

        # scale rig_add
        if self.rig_add:
            self.rig_add.location *= self.scale_ratio
            self.rig_add.scale *= self.scale_ratio
            self.scale_fixed_objects.append(self.rig_add.name)

    if scn.arp_bind_sel_verts:
        print("Saving non-selected vertices weights...")
        for obj_name in self.obj_to_skin:            
            obj = get_object(obj_name)
            non_selected_verts_dict[obj_name] = [v.index for v in obj.data.vertices if v.select == False]
            weight_list = []
            for vi in non_selected_verts_dict[obj_name]:               
                for vgroup in obj.data.vertices[vi].groups:
                    weight_list.append([obj.vertex_groups[vgroup.group].name, vi, vgroup.weight])

            weights_dict[obj_name] = weight_list

    
    # Particle system on the mesh? If so operate on a duplicate to preserve particles vertex groups, and transfer weights back at the end
    if scn.arp_bind_engine == "HEAT_MAP":
        for obj_name in self.obj_to_skin.copy():
            obj = get_object(obj_name)
            if len(obj.modifiers):
                for mod in obj.modifiers:
                    if mod.type == "PARTICLE_SYSTEM":
                        bpy.ops.object.select_all(action='DESELECT')
                        set_active_object(obj.name)
                        duplicate_object()
                        
                        if get_object(obj.name + "_arp_temp_skin"):#should not exist, leftover from a previous buggy session
                            delete_object(get_object(obj.name + "_arp_temp_skin"))
                            
                        bpy.context.active_object.name = obj.name + "_arp_temp_skin"
                        
                        if len(bpy.context.active_object.vertex_groups):
                            bpy.ops.object.vertex_group_remove(all=True)

                        self.obj_to_skin.remove(obj.name)
                        self.obj_to_skin.append(bpy.context.active_object.name)
                        
                        break

    # High resolution meshes? If so reduce the polycount, and transfer weights back at the end
    if scn.arp_optimize_highres and scn.arp_bind_engine == "HEAT_MAP":
        for obj_name in self.obj_to_skin.copy():
            obj = get_object(obj_name)

            if len(obj.data.polygons) > scn.arp_highres_threshold:
                print("Found high res mesh:", obj.name)
                bpy.ops.object.select_all(action='DESELECT')
                set_active_object(obj.name)
                duplicate_object()
                
                if get_object(obj.name + "_arp_temp_skin"):#should not exist, leftover from a previous buggy session
                    delete_object(get_object(obj.name + "_arp_temp_skin"))
                    
                bpy.context.active_object.name = obj.name + "_arp_temp_skin"
                
                # remove vgroups
                if len(bpy.context.active_object.vertex_groups):
                    bpy.ops.object.vertex_group_remove(all=True)

                # apply existing modifiers if any
                bpy.ops.object.convert(target='MESH')
                # decimate
                decim_mod = bpy.context.active_object.modifiers.new("decimate", "DECIMATE")
                decim_mod.ratio = 0.2
                bpy.ops.object.convert(target='MESH')

                self.obj_to_skin.remove(obj.name)
                self.obj_to_skin.append(bpy.context.active_object.name)

    # If pseudo-voxels is used, operate on a single voxelized mesh
    if scn.arp_bind_engine == "PSEUDO_VOXELS":
        print("Voxelizing...")
        vox_time_start = time.time()
        # duplicate
        bpy.ops.object.select_all(action='DESELECT')

        for obj_name in self.obj_to_skin_save:
            obj = get_object(obj_name)
            # skip auto-skinned eyeballs, if defined
            if self.auto_eyeballs:
                if is_eyeball_objects(obj, scn):
                    continue

            set_active_object(obj_name)
            # remove the objects from the list, only the joined mesh will be treated
            # and weights are transferred at the end to each object
            self.obj_to_skin.remove(obj_name)

        duplicate_object()

        # convert to mesh
        bpy.ops.object.convert(target='MESH')

        # join
        voxelized_object = None
        if len(self.obj_to_skin_save) > 1:
            bpy.ops.object.join()

        voxelized_object = get_object(bpy.context.active_object.name)

        # remove vgroups
        if len(voxelized_object.vertex_groups) > 0:
            bpy.ops.object.vertex_group_remove(all=True)

        voxelized_object.name = "arp_full_character_voxelized"

        self.obj_to_skin.append(voxelized_object.name)

        # remesh
        print("  remeshing...")
        object_dim = [dim for dim in voxelized_object.dimensions]

        # get mesh dimensions
        larger_dim = 0
        for d in object_dim:
            if d > larger_dim:
                larger_dim = d

        larger_scale = 0
        for s in voxelized_object.scale:
            if s > larger_scale:
                larger_scale = s

        if scn.arp_pseudo_voxels_type == "1":
            mod = bpy.context.active_object.modifiers.new('remesh', 'REMESH')
            mod.mode = 'SHARP'
            mod.scale = 0.95  # gets better details than default 0.9
            mod.threshold = 0.82  # make sure large separate pieces are not removed
            mod.octree_depth = scn.arp_pseudo_voxels_resolution
            mod.use_remove_disconnected = True

            bpy.context.evaluated_depsgraph_get().update()

            # check if remeshing went wrong, by comparing before and after dimensions
            remesh_valid_dim = [False, False, False]
            remesh_iter = 0

            while remesh_valid_dim != [True, True, True] and remesh_iter < 3:
                for idx, dim in enumerate(object_dim):
                    current_dim = bpy.context.active_object.dimensions[idx]
                    error_rate = abs((current_dim / dim) - 1)
                    if error_rate > 0.04:
                        print("  invalid mesh (error rate:", round(error_rate, 2), "), fixing...")
                        if remesh_iter == 0:
                            mod.scale = 0.9
                        if remesh_iter == 1:
                            mod.use_remove_disconnected = False
                        if remesh_iter == 2:
                            print("  could not validate voxelized mesh!")

                        bpy.context.evaluated_depsgraph_get().update()
                        remesh_iter += 1
                        remesh_valid_dim[idx] = False
                        break
                    else:
                        remesh_valid_dim[idx] = True

        elif scn.arp_pseudo_voxels_type == "2":
            mod = voxelized_object.modifiers.new('remesh', 'REMESH')
            mod.mode = 'VOXEL'
            voxel_base_res = scn.arp_pseudo_voxels_resolution
            if voxel_base_res == 7:
                voxel_base_res = 6
            elif voxel_base_res == 9:
                voxel_base_res = 10

            mod.voxel_size = ((larger_dim/larger_scale)*0.003) / (voxel_base_res/8)
            print("Voxel Size", mod.voxel_size)

        bpy.ops.object.convert(target='MESH')

        # simplify meshes if polycount higher than 60k poly for performances
        while_counter = 0
        dist_thresh_fac = 1000
        #if len(voxelized_object.data.polygons) > 120000:
        #    dist_thresh_fac = 500

        dist_threshold = (larger_dim / larger_scale) / dist_thresh_fac
        remove_amount = 0

        while len(voxelized_object.data.polygons) > 70000 and while_counter < 40:
            print(' ', len(voxelized_object.data.polygons), "polygons, decimating...")
            base_vert_count = len(voxelized_object.data.vertices)
            base_face_count = len(voxelized_object.data.polygons)

            bpy.ops.object.mode_set(mode='EDIT')
            bpy.ops.mesh.select_all(action='SELECT')
            bpy.ops.mesh.remove_doubles(threshold=dist_threshold)
            bpy.ops.object.mode_set(mode='OBJECT')
            current_face_count = len(voxelized_object.data.polygons)
            remove_amount = base_face_count - current_face_count
            #print("Removed", remove_amount, "faces")
            #print(' ', base_face_count, 'faces to >', current_face_count, 'faces')

            # more accurate when reaching limit
            dist_fac = 1.3
            if current_face_count < 120000 and remove_amount > 30000:
                dist_fac = 1.15
            if current_face_count < 100000 and remove_amount > 30000:
                dist_fac = 1.06
            if current_face_count > 200000 and remove_amount < 500:
                dist_fac = 4

            dist_threshold *= dist_fac

            while_counter += 1

        bpy.ops.object.mode_set(mode='OBJECT')

        print(' ', len(voxelized_object.data.vertices), 'vertices',
              len(voxelized_object.data.polygons), 'faces')
        print("Voxelized in ", round(time.time() - vox_time_start, 2), "seconds")

    
    # Compute skinning
    for obj_name in self.obj_to_skin:
        obj = get_object(obj_name)
        has_duplicated = False

        if self.auto_eyeballs:
            ret = bind_skin_eyeballs(obj, self)
            if ret == "continue":
                continue

        # split loose parts in option for better auto-skinning
        if scn.arp_bind_split and scn.arp_bind_engine == "HEAT_MAP":
            # duplicate to preserve vertex ID when splitting
            if not "_arp_temp_skin" in obj_name:
                bpy.ops.object.mode_set(mode='OBJECT')
                bpy.ops.object.select_all(action='DESELECT')
                set_active_object(obj_name)
                duplicate_object()
                bpy.context.active_object.name = obj_name + "_arp_copy"
                obj = bpy.data.objects.get(obj_name + "_arp_copy")
                has_duplicated = True

            print("Split...")
            bpy.ops.object.mode_set(mode='OBJECT')
            bpy.ops.object.select_all(action='DESELECT')
            set_active_object(obj.name)
            bpy.ops.mesh.separate(type='LOOSE')
            split_objects = [split_obj for split_obj in bpy.context.selected_objects if split_obj.type == "MESH"]
        else:
            split_objects = [obj]

        # compute skinning
        for i, split_obj in enumerate(split_objects):
            #print('skinning object:', str(i + 1) + '/' + str(len(split_objects)))
            sys.stdout.write('\r  '+'Skinning object part: '+str(i+1)+'/'+ str(len(split_objects))+'                                         ')
            sys.stdout.flush()

            def get_armature_mod(_name):
                obj = bpy.context.active_object
                for mod in obj.modifiers:
                    if mod.type == "ARMATURE":
                        if mod.object:
                            if mod.object.name == _name:
                                return mod

            if self.bind_rig_add:
                # bind to rig add
                bpy.ops.object.mode_set(mode='OBJECT')
                bpy.ops.object.select_all(action='DESELECT')
                set_active_object(split_obj.name)
                set_active_object(self.rig_add.name)

                bpy.ops.object.parent_set(type='ARMATURE_AUTO')

                set_active_object(split_obj.name)
                get_armature_mod(self.rig_add.name).name = "rig_add"

            if self.bind_rig:
                # bind to rig
                bpy.ops.object.mode_set(mode='OBJECT')
                bpy.ops.object.select_all(action='DESELECT')
                set_active_object(split_obj.name)
                set_active_object(self.rig.name)

                bpy.ops.object.parent_set(type='ARMATURE_AUTO')

                set_active_object(split_obj.name)
                rig_mod = get_armature_mod(self.rig.name)
                rig_mod.name = "rig"
                rig_mod.show_in_editmode = True
                rig_mod.show_on_cage = True
                rig_mod.use_deform_preserve_volume = scn.arp_bind_preserve

        # merge the split objects
        print('\n')

        bpy.ops.object.select_all(action='DESELECT')
        if len(split_objects) > 1:
            for split_obj in split_objects:
                set_active_object(split_obj.name)

        set_active_object(obj.name)

        if len(split_objects) > 1:
            bpy.ops.object.join()

        # transfer weights, vertices location based
        if has_duplicated:
            bpy.ops.object.select_all(action='DESELECT')
            set_active_object(obj_name)
            set_active_object(obj_name + "_arp_copy")

            # Mask modifiers prevent correct data transfer
            disable_dict = disable_mask_modifiers(bpy.context.selected_objects)

            bpy.ops.object.data_transfer(data_type='VGROUP_WEIGHTS', vert_mapping='NEAREST', layers_select_src='ALL', layers_select_dst='NAME')

            restore_mask_modifiers(disable_dict)

            # remove duplicate
            delete_object(get_object(obj_name + "_arp_copy"))
            set_active_object(obj_name)
        
        # add armature modifiers
        add_armature_modifiers(self)

        # improve, finalize weights
        bpy.ops.object.mode_set(mode='OBJECT')
        set_active_object(obj_name)
        body = get_object(obj_name)

        bind_improve_weights(body, self)

        # Unselect all verts
        print("  Unselect...")
        for v in bpy.context.active_object.data.vertices:
            v.select = False


        # End loop objects
    
    bpy.ops.object.mode_set(mode='OBJECT')
    
    # Particles modifier or high resolution case: finally transfer weights from temp to original objects if any
    for obj_name in self.obj_to_skin:
        obj = get_object(obj_name)        

        # skip auto-skinned eyeballs, if defined by the Smart detection
        if self.auto_eyeballs:
            if is_eyeball_objects(obj, scn):
                continue

        if "_arp_temp_skin" in obj_name:
            bpy.ops.object.select_all(action='DESELECT')
            
            # select source object
            source_obj_name = obj_name.replace("_arp_temp_skin", "")
            if get_object(source_obj_name) == None:# renaming went wrong, name conflict
                continue
            set_active_object(source_obj_name)

            add_armature_modifiers(self)

            # disable modifiers temporarily for weight transfers
            mod_save = []
            for mod in bpy.context.active_object.modifiers:
                mod_save.append(mod.show_viewport)
                mod.show_viewport = False

                # select target object
            set_active_object(obj.name)
            for mod in bpy.context.active_object.modifiers:
                mod.show_viewport = False

                # Transfer weights
            bpy.ops.object.data_transfer(data_type='VGROUP_WEIGHTS', vert_mapping='POLYINTERP_NEAREST', layers_select_src='ALL', layers_select_dst='NAME')

            # Clean weights
            set_active_object(source_obj_name)
            bpy.ops.object.vertex_group_clean(group_select_mode='ALL', limit=0.01)

            # Restore modifiers states
            for i, mod in enumerate(bpy.context.active_object.modifiers):
                mod.show_viewport = mod_save[i]

            # Remove temp object
            bpy.data.objects.remove(obj, do_unlink=True)

    
    # Voxelized case: finally transfer weights from voxelized to original objects
    if scn.arp_bind_engine == "PSEUDO_VOXELS":
        voxelized_object = bpy.data.objects.get("arp_full_character_voxelized")

        for obj_name in self.obj_to_skin_save:
            bpy.ops.object.select_all(action='DESELECT')
            obj = get_object(obj_name)

            # skip auto-skinned eyeballs, if defined by the Smart detection
            if self.auto_eyeballs:
                if is_eyeball_objects(obj, scn):
                    continue

            # select source object
            set_active_object(obj_name)

            # add armature modifiers
            add_armature_modifiers(self)

            # make sure to disable weight paint vertex selection
            bpy.context.active_object.data.use_paint_mask_vertex = False

            # disable modifiers temporarily for weight transfers
            mod_save = []
            for mod in bpy.context.active_object.modifiers:
                mod_save.append(mod.show_viewport)
                mod.show_viewport = False

            #   select target object
            set_active_object(voxelized_object.name)
            for mod in bpy.context.active_object.modifiers:
                mod.show_viewport = False

                # Transfer weights
            bpy.ops.object.data_transfer(data_type='VGROUP_WEIGHTS', vert_mapping='POLYINTERP_NEAREST', layers_select_src='ALL', layers_select_dst='NAME')
            print("Transferred voxelized weights:", obj_name)

            # Clean weights
            set_active_object(obj_name)
            try:
                bpy.ops.object.vertex_group_clean(group_select_mode='ALL', limit=0.01)
            except:
                pass

            # Restore modifiers states
            for i, mod in enumerate(bpy.context.active_object.modifiers):
                mod.show_viewport = mod_save[i]

            # fix vertices with no weights if any
            fix_verts = True
            fix_vert_timestart = time.time()

            if fix_verts:
                #print("\nFixing weights...")

                for i in range(0, 2):# apply two times, hack to fix remaining issues...
                    edges = [e.vertices for e in obj.data.edges]
                    verts_no_weight = []
                    fixed_weight_verts = []

                    for v in obj.data.vertices:
                        if (time.time() - fix_vert_timestart) > 6:# limit search to 6 seconds, if more, the skinning is probably invalid
                            break

                        if len(v.groups) == 0:
                            verts_no_weight.append(v.index)

                            # look for a connected vert
                            vert_is_fixed = False

                            for edge_verts in edges:

                                if vert_is_fixed:
                                    break

                                if v.index in edge_verts:
                                    for edge_vert_idx in edge_verts:
                                        new_vert = obj.data.vertices[edge_vert_idx]
                                        if len(new_vert.groups):
                                            if v.index in fixed_weight_verts:
                                                continue

                                            for grp in new_vert.groups:
                                                grp_idx = grp.group
                                                grp_weight = grp.weight
                                                vertex_group = obj.vertex_groups[grp_idx]
                                                vertex_group.add([v.index], grp_weight, 'REPLACE')
                                                vert_is_fixed = True

                                            fixed_weight_verts.append(v.index)

                    if len(fixed_weight_verts):
                        print("Fixed "+ str(len(fixed_weight_verts)) + " vertices weights out of "+str(len(verts_no_weight)))


    # Assign skinned objects to collection
    bind_set_collec(self)
    
    if 'obj' in locals():
        del obj
    
    # Restore non-selected vertices weights    
    if scn.arp_bind_sel_verts:
        print("Restoring non-selected verts weights...")
        
        for obj_name in non_selected_verts_dict:
            obj = get_object(obj_name)
            
            # restore vert selection, select all then unselect others
            for vert in obj.data.vertices:
                vert.select = True
                
            # remove all weights of non-selected vertices
            for vi in non_selected_verts_dict[obj_name]:              
                vert = obj.data.vertices[vi]
                for grp in vert.groups:
                    grp.weight = 0.0
                # unselect
                vert.select = False

            # restore vertices weight            
            for grp_name, vi, vertex_weight in weights_dict[obj_name]:
                obj.vertex_groups[grp_name].add([vi], vertex_weight, 'REPLACE')

            # remove weights too low
            set_active_object(obj_name)
            try:
                bpy.ops.object.vertex_group_clean(group_select_mode='ALL', limit=0.01)
            except:# error if no vertex groups
                pass


        print("Restored.")

    
    bind_finalize(self)
    
    # Restore scale fix
    restore_scale_fix(self)
    
    # Parent meshes to the rig
    bind_parent(self)
    
    print("Bound in " + str(round(time.time() - time_start, 2)) + " seconds.")
    self.binding_time = time.time() - time_start


def _unbind_to_rig(full_unbind=False):
    print('Unbinding...')

    scn = bpy.context.scene
    rig = bpy.context.active_object
    additive_controllers = (rig.arp_secondary_type == "ADDITIVE")

    def del_vgroup(obj, grp):
        deleted = False

        vgroup = obj.vertex_groups.get(grp)
        if vgroup:
            obj.vertex_groups.remove(vgroup)
            deleted = True

        return deleted

    vgroup_delete_count = 0
    prefs = bpy.context.preferences.addons[__package__].preferences
    
    for obj in bpy.context.selected_objects:
        if obj.type == 'MESH':
            set_active_object(obj.name)

            # delete modifiers
            if len(obj.modifiers):
                used_vgroups = []
                
                for mod in obj.modifiers:
                    if prefs.remove_existing_arm_mods:
                        if mod.type == 'ARMATURE':
                            try:
                                obj.modifiers.remove(mod)
                                continue
                            except:
                                print('Cannot delete modifier:', mod.name)
                    # check if some vgroups are actually used by existing modifiers
                    if 'vertex_group' in dir(mod):
                        if mod.vertex_group != '':
                            used_vgroups.append(mod.vertex_group)
                    
                    #   hair modifiers
                    if mod.type == 'PARTICLE_SYSTEM':
                        for ps in obj.particle_systems:
                            for attr in dir(ps):
                                if attr.startswith('vertex_group'):
                                    vg_name = getattr(ps, attr)
                                    if vg_name != '':
                                        used_vgroups.append(vg_name)
                    
                # delete vertex groups
                if (scn.arp_bind_sel_verts == False and scn.arp_bind_selected_bones == False) or full_unbind:# don't delete if exclude_selected is enabled                    
                    if len(obj.vertex_groups):                        
                        for vgroup in obj.vertex_groups:                        
                            if vgroup.name.startswith('00_'):# mmh... arbitrary convention used by... me?
                                continue
                            if vgroup.lock_weight == True:
                                continue
                            if vgroup.name in used_vgroups:                                
                                continue
                            obj.vertex_groups.remove(vgroup)

                # delete the rig_add bend bones if not necessary
                elif not additive_controllers:
                    for grp_name in ard.leg_bones_rig_add + ard.arm_bones_rig_add + ard.spine_bones_rig_add + ["null_bend.x"]:
                        if grp_name.endswith(".x"):
                            has_deleted = del_vgroup(obj, grp_name)
                            if has_deleted:
                                vgroup_delete_count += 1
                        else:
                            for side in [".l", ".r"]:
                                grp = grp_name + side
                                has_deleted = del_vgroup(obj, grp)
                                if has_deleted:
                                    vgroup_delete_count += 1

            # clear parent and keep transforms
            obj_mat = obj.matrix_world.copy()
            obj.parent = None
            obj.matrix_world = obj_mat

    if vgroup_delete_count > 0:
        print("Deleted", vgroup_delete_count, "rig_add vgroups")

    print("Unbound.")


def _edit_ref(deselect=True):
    # display layer 17 only
    _layers = bpy.context.active_object.data.layers
    # must enabling one before disabling others
    _layers[17] = True
    for i in range(0, 32):
        if i != 17:
            _layers[i] = False

            # set X-Ray
    bpy.context.active_object.show_in_front = True

    bpy.ops.object.mode_set(mode='EDIT')
    if deselect:
        bpy.ops.armature.select_all(action='DESELECT')


def _finalize_armature(self):
    scn = bpy.context.scene

    # set all deforming bones in layer 31
    for b in bpy.context.active_object.data.bones:
        b.layers[31] = b.use_deform

    # Restore the proxy picker state
    try:
        scn.Proxy_Picker.active = self.state_proxy_picker
    except:
        pass

    # Restore x-axis mirror edit
    bpy.context.active_object.data.use_mirror_x = self.state_xmirror

    # Display layers 0, 1 only
    _layers = bpy.context.active_object.data.layers
    # must enabling one before disabling others
    _layers[0] = True
    for i in range(0, 32):
        if i > 1:
            _layers[i] = False

    remove_invalid_drivers()
    remove_duplicated_drivers()


def _initialize_armature(self):
    scn = bpy.context.scene

    # Disable the proxy picker to avoid bugs
    try:
        self.state_proxy_picker = scn.Proxy_Picker.active
        scn.Proxy_Picker.active = False
    except:
        pass

        # Switch to Edit mode
    # DEBUG: switch to Pose mode before, otherwise may lead to random crash with 2.8
    bpy.ops.object.mode_set(mode='POSE')
    bpy.ops.object.mode_set(mode='EDIT')

    # Disable x-axis mirror edit
    self.state_xmirror = bpy.context.active_object.data.use_mirror_x
    bpy.context.active_object.data.use_mirror_x = False

    # Active all layers
    layers_select = enable_all_armature_layers()


def set_master_controller():
    if get_edit_bone("c_master") == None:
        print("Missing c_master, adding it...")
        c_pos = get_edit_bone("c_pos")
        master = bpy.context.active_object.data.edit_bones.new("c_master")
        master.head, master.tail, master.roll = c_pos.head.copy(), c_pos.tail.copy(), c_pos.roll
        master.tail = master.head + (master.tail - master.head) * 0.85

        master.parent = c_pos
        c_traj = get_edit_bone("c_traj")
        if c_traj == None:
            return
        c_traj.parent = master

        set_bone_layer(master, 0)

        bpy.ops.object.mode_set(mode='POSE')
        # set custom shape
        master_pbone = get_pose_bone("c_master")
        c_pos_pbone = get_pose_bone("c_pos")
        master_pbone.custom_shape = c_pos_pbone.custom_shape
        # set euler
        master_pbone.rotation_mode = "XYZ"
        # set color group
        master_pbone.bone_group = c_pos_pbone.bone_group

        bpy.ops.object.mode_set(mode='EDIT')


def _set_masters():
    # create it
    bpy.ops.object.mode_set(mode='EDIT')

    c_pos = get_edit_bone("c_pos")
    if c_pos == None:
        print("Missing c_pos, cannot set master bones")
        return

    # Master "master" controller needed for TNZPV rigs
    if tnzpv:
        set_master_controller()

    rig = bpy.context.active_object

    if rig.arp_master_fly:  # add c_fly master
        if get_edit_bone("c_fly") == None:
            print("Missing c_fly, adding it...")
            c_fly = bpy.context.active_object.data.edit_bones.new("c_fly")
            c_fly.head, c_fly.tail, c_fly.roll = c_pos.head.copy(), c_pos.tail.copy(), c_pos.roll
            c_fly.tail = c_fly.head + (c_fly.tail - c_fly.head) * 0.75

            c_traj = get_edit_bone("c_traj")
            # change children
            for child in c_traj.children:
                child.parent = c_fly

            c_fly.parent = c_traj

            set_bone_layer(c_fly, 0)

            bpy.ops.object.mode_set(mode='POSE')
            # set custom shape
            fly_pbone = get_pose_bone("c_fly")
            c_pos_pbone = get_pose_bone("c_pos")
            cs_fly = bpy.data.objects.get("cs_fly")
            if cs_fly == None:
                append_from_arp(nodes=["cs_fly"], type="object")
                cs_fly = bpy.data.objects.get("cs_fly")
            fly_pbone.custom_shape = cs_fly
            # set euler
            fly_pbone.rotation_mode = "XYZ"
            # set color group
            fly_pbone.bone_group = c_pos_pbone.bone_group

            # set constraints
            for pbone in rig.pose.bones:
                if len(pbone.constraints):
                    for cns in pbone.constraints:
                        if not "target" in dir(cns):
                            continue
                        if cns.target == rig:
                            if cns.subtarget == "c_traj":
                                cns.subtarget = "c_fly"

            bpy.ops.object.mode_set(mode='EDIT')

    else:  # remove c_fly master
        if get_edit_bone("c_fly"):
            print("Removing c_fly...")
            c_fly = get_edit_bone("c_fly")
            c_traj = get_edit_bone("c_traj")
            bpy.context.active_object.data.edit_bones.remove(c_fly)

            bpy.ops.object.mode_set(mode='POSE')
            # set constraints
            for pbone in rig.pose.bones:
                if len(pbone.constraints):
                    for cns in pbone.constraints:
                        if not "target" in dir(cns):
                            continue
                        if cns.target == rig:
                            if cns.subtarget == "c_fly":
                                cns.subtarget = "c_traj"

            bpy.ops.object.mode_set(mode='EDIT')


def _align_arm_limbs():
    print("\n Aligning arm bones...\n")

    scn = bpy.context.scene
    sides = limb_sides.arm_sides

    rig = get_object(bpy.context.active_object.name)

    shoulder_ref_name = ard.arm_ref_dict['shoulder']
    arm_ref_name = ard.arm_ref_dict['arm']
    forearm_ref_name = ard.arm_ref_dict['forearm']
    hand_ref_name = ard.arm_ref_dict['hand']

    shoulder_name = ard.arm_bones_dict["shoulder"]["deform"]
    c_shoulder_name = ard.arm_bones_dict["shoulder"]["control"]
    shoulder_pole_name = ard.arm_bones_dict['shoulder']['pole']#"shoulder_pole"
    shoulder_track_pole_name = ard.arm_bones_dict['shoulder']['track_pole']#"shoulder_track_pole"

    c_arm_ik_name = ard.arm_bones_dict['arm']['control_ik']
    c_arm_fk_name = ard.arm_bones_dict['arm']['control_fk']
    arm_fk_name = ard.arm_bones_dict['arm']['fk']
    arm_ik_nostr_name = ard.arm_bones_dict['arm']['ik_nostr']
    arm_ik_scale_fix_name = ard.arm_bones_dict['arm']['ik_scale_fix']
    arm_ik_name = ard.arm_bones_dict['arm']['ik']
    arm_twist_name = ard.arm_bones_dict['arm']['twist']
    arm_twist_twk_name = ard.arm_bones_dict['arm']['twist_twk']
    arm_stretch_name = ard.arm_bones_dict['arm']['stretch']
    arm_name = ard.arm_bones_dict['arm']['base']
    arm_twist_offset_name = ard.arm_bones_dict['arm']['control_twist']#"c_arm_twist_offset"

    c_forearm_fk_name = ard.arm_bones_dict["forearm"]["control_fk"]
    forearm_fk_name = ard.arm_bones_dict["forearm"]["fk"]
    forearm_ik_nostr_name = ard.arm_bones_dict["forearm"]["ik_nostr"]
    forearm_ik_name = ard.arm_bones_dict["forearm"]["ik"]
    forearm_twist_name = ard.arm_bones_dict["forearm"]["twist"]
    forearm_stretch_name = ard.arm_bones_dict["forearm"]["stretch"]
    forearm_name = ard.arm_bones_dict["forearm"]["base"]

    hand_name = ard.arm_bones_dict["hand"]["deform"]
    c_hand_ik_name = ard.arm_bones_dict["hand"]["control_ik"]
    c_hand_fk_name = ard.arm_bones_dict["hand"]["control_fk"]
    hand_scale_fix_name = ard.arm_bones_dict["hand"]["fk_scale_fix"]
    hand_rot_twist_name = ard.arm_bones_dict['hand']['rot_twist']#"hand_rot_twist"

    prepole_name = ard.arm_bones_dict['prepole']
    fk_pole_name = ard.arm_bones_dict['fk_pole']#"arm_fk_pole"
    ik_pole_name = ard.arm_bones_dict['control_pole_ik']#"c_arms_pole"
    c_stretch_arm_name = ard.arm_bones_dict['control_stretch']#"c_stretch_arm"
    arm_pin_name = ard.arm_bones_dict['control_pin']

    c_shoulder_bend_name = ard.arm_bones_dict['arm']['secondary_00']
    c_arm_bend_name = ard.arm_bones_dict['arm']['secondary_01']
    c_elbow_bend_name = ard.arm_bones_dict['forearm']['secondary_00']
    c_forearm_bend_name = ard.arm_bones_dict['forearm']['secondary_01']
    c_wrist_bend_name = ard.arm_bones_dict['forearm']['secondary_02']

    arm_bendy_name = ard.arm_bendy_dict['arm']
    forearm_bendy_name = ard.arm_bendy_dict['forearm']

    shoulders = [shoulder_name, c_shoulder_name]
    arms = [c_arm_ik_name, c_arm_fk_name, arm_fk_name, arm_ik_nostr_name, arm_ik_scale_fix_name, arm_ik_name, arm_twist_name, arm_twist_twk_name, arm_stretch_name, arm_name, arm_twist_offset_name]
    forearms = [c_forearm_fk_name, forearm_fk_name, forearm_ik_nostr_name, forearm_ik_name, forearm_twist_name, forearm_stretch_name, forearm_name]
    arm_bends = [c_shoulder_bend_name, c_arm_bend_name, c_elbow_bend_name, c_forearm_bend_name, c_wrist_bend_name]


    bpy.ops.object.mode_set(mode='EDIT')
    
    # get ik-fk default value
    arms_ikfk_default_dict = {}
    for side in sides:
        hand_ref = get_edit_bone(hand_ref_name+side)
        
        if 'ikfk_default' in hand_ref.keys():
            val = hand_ref['ikfk_default']
            if val == "IK" or val == "FK":
                arms_ikfk_default_dict[side] = val
                continue        
                
        arms_ikfk_default_dict[side] = bpy.context.preferences.addons[__package__].preferences.default_ikfk_arm
    
    # arms
    for side in sides:
        print("  [", side, "]")
        ref_arm = get_edit_bone(arm_ref_name+side)
        arm_vec = ref_arm.tail - ref_arm.head

        # c_arm_ik
        c_arm_ik = get_edit_bone(c_arm_ik_name+side)
        copy_bone_transforms(ref_arm, c_arm_ik)

        # c_arm_fk
        c_arm_fk = get_edit_bone(c_arm_fk_name+side)
        copy_bone_transforms(ref_arm, c_arm_fk)

        # parent
        shoulder_ref = get_edit_bone(shoulder_ref_name+side)
        
        arm_fk_lock = False# if arm FK lock setting is enabled, use constraint instead of direct parent
        if 'arm_fk_lock' in ref_arm.keys():
            arm_fk_lock = ref_arm['arm_fk_lock']

        if arm_fk_lock:
            c_arm_fk.parent = None
        else:
            if shoulder_ref.parent:
                c_arm_fk.parent = parent_retarget(shoulder_ref)
            else:
                c_arm_fk.parent = get_edit_bone(get_first_master_controller())

        # arm_fk
        arm_fk = get_edit_bone(arm_fk_name+side)
        copy_bone_transforms(ref_arm, arm_fk)

        # arm_ik_nostr
        arm_ik_nostr = get_edit_bone(arm_ik_nostr_name+side)
        copy_bone_transforms(ref_arm, arm_ik_nostr)

        # arm_ik_scale_fix
        arm_ik_scale_fix = get_edit_bone(arm_ik_scale_fix_name+side)
        copy_bone_transforms(ref_arm, arm_ik_scale_fix)

        # arm_ik
        arm_ik = get_edit_bone(arm_ik_name+side)
        copy_bone_transforms(ref_arm, arm_ik)

        # arm_twist
        arm_twist = get_edit_bone(arm_twist_name+side)
        arm_twist.head = ref_arm.head.copy()
        arm_twist.tail = ref_arm.head + (arm_vec * 0.5)

        # arm_twist_twk
        arm_twist_twk = get_edit_bone(arm_twist_twk_name+side)
        #print(arm_twist_twk, ref_arm)
        arm_twist_twk.head = ref_arm.head.copy()
        arm_twist_twk.tail = ref_arm.head + (arm_vec * 0.5)

        # arm_stretch
        arm_stretch = get_edit_bone(arm_stretch_name+side)
        if rig.arp_secondary_type == "BENDY_BONES":
            arm_stretch.bbone_segments = 20
            arm_stretch.head = ref_arm.head.copy()
            arm_stretch.tail = ref_arm.tail.copy()
        else:
            arm_stretch.bbone_segments = 0
            arm_stretch.head = ref_arm.head + (ref_arm.tail - ref_arm.head) * 0.5
            arm_stretch.tail = ref_arm.tail.copy()

        # arm
        arm = get_edit_bone(arm_name+side)
        copy_bone_transforms(ref_arm, arm)

        # arm_twist_offset
        arm_twist_offset = get_edit_bone(arm_twist_offset_name+side)
        arm_twist_offset.head = ref_arm.head.copy()
        arm_twist_offset.tail = ref_arm.head + (arm_vec * 0.4)
        

    # Delete drivers of bendy bones if any. Must be done now, generates cyclic dependencies and possible crash otherwise
    bones_names_todel = []

    if rig.arp_secondary_type != "BENDY_BONES":
        bones_names_todel += [arm_stretch_name, forearm_stretch_name]
    if rig.arp_secondary_type != "TWIST_BASED":
        bones_names_todel += [arm_bendy_name, forearm_bendy_name]

    if len(bones_names_todel):
        drivers_list = rig.animation_data.drivers
        deleted_drivers_count = 0

        prop_list = ['bbone_curveinx', get_bbone_param_name('bbone_curveinz'), 'bbone_curveoutx', get_bbone_param_name('bbone_curveoutz'),
                     'bbone_scalein', 'bbone_scaleout',
                     'bbone_rollin', 'bbone_rollout', 'bbone_easein', 'bbone_easeout']

        for side in sides:
            for dri in drivers_list:
                found = False

                bname = get_pbone_name_from_data_path(dri.data_path)
                if bname == None:# not a bone driver
                    continue
                    
                for bn in bones_names_todel:
                    if bn + side == bname:
                        found = True
                        break

                if not found:
                    continue

                found_prop = False

                for p_i in prop_list:
                    if p_i in dri.data_path:
                        found_prop = True
                        break

                if not found_prop:
                    continue

                try:
                    rig.driver_remove(dri.data_path, -1)
                except:
                    # something prevents to remove the driver. A workaround is to change the data_path before removing.
                    dri.data_path = "delta_scale"
                    rig.driver_remove(dri.data_path, -1)

                deleted_drivers_count += 1

        print("  Deleted", deleted_drivers_count, "drivers")

    for side in sides:
        print("  [", side, "]")

        forearm_ref = get_edit_bone(forearm_ref_name + side)

        if forearm_ref:
            # forearm
            forearm = get_edit_bone(forearm_name + side)
            copy_bone_transforms(forearm_ref, forearm)

            # c_forearm_fk
            c_forearm_fk = get_edit_bone(c_forearm_fk_name + side)
            copy_bone_transforms(forearm_ref, c_forearm_fk)

            # forearm_fk
            forearm_fk = get_edit_bone(forearm_fk_name + side)
            copy_bone_transforms(forearm_ref, forearm_fk)

            # forearm_ik_nostr
            forearm_ik_nostr = get_edit_bone(forearm_ik_nostr_name + side)
            copy_bone_transforms(forearm_ref, forearm_ik_nostr)

            # forearm_ik
            forearm_ik = get_edit_bone(forearm_ik_name + side)
            copy_bone_transforms(forearm_ref, forearm_ik)

            # forearm_twist
            forearm_twist = get_edit_bone(forearm_twist_name + side)
            forearm_twist.head = forearm_ref.head + (forearm_ref.tail - forearm_ref.head) * 0.5
            forearm_twist.tail = forearm_ref.tail.copy()

            # forearm_stretch
            forearm_stretch = get_edit_bone(forearm_stretch_name + side)

            if rig.arp_secondary_type == "BENDY_BONES":
                forearm_stretch.bbone_segments = 20
                forearm_stretch.head = forearm_ref.head.copy()
                forearm_stretch.tail = forearm_ref.tail.copy()
            else:
                forearm_stretch.bbone_segments = 0
                forearm_stretch.head = forearm_ref.head.copy()
                forearm_stretch.tail = forearm_ref.head + (forearm_ref.tail - forearm_ref.head) * 0.5


        for bname in shoulders:
            current_bone = get_edit_bone(bname + side)
            ref_bone = get_edit_bone(shoulder_ref_name + side)

            if current_bone and ref_bone:
                copy_bone_transforms(ref_bone, current_bone)

                # parent bone
                if 'c_' in bname:
                    if ref_bone.parent:
                        current_bone.parent = parent_retarget(ref_bone)
                    else:
                        current_bone.parent = get_edit_bone(get_first_master_controller())

        if "bname" in locals():
            del bname


    # align secondary bones
    def align_arm_bend_bones(side):
        arm_ref = get_edit_bone(arm_ref_name+side)
        forearm_ref = get_edit_bone(forearm_ref_name+side)
        length = 0.07

        for bname in arm_bends:
            current_bone = get_edit_bone(bname+side)

            if current_bone:
                if bname == c_shoulder_bend_name:
                    current_bone.head = arm_ref.head + (arm_ref.tail - arm_ref.head) * 0.3
                    current_bone.tail = current_bone.head + (arm_ref.y_axis * length * arm_ref.length * 3)
                    current_bone.roll = arm_ref.roll

                if bname == c_arm_bend_name:
                    arm_vec = arm_ref.tail - arm_ref.head
                    current_bone.head = arm_ref.head + arm_vec * 0.6
                    current_bone.tail = current_bone.head + (arm_ref.y_axis * length * arm_ref.length * 3)
                    current_bone.roll = arm_ref.roll

                if bname == c_elbow_bend_name:
                    current_bone.head = arm_ref.tail
                    current_bone.tail = current_bone.head + (arm_ref.y_axis * length * arm_ref.length * 3)
                    current_bone.roll = arm_ref.roll

                if bname == c_forearm_bend_name:
                    arm_vec = forearm_ref.tail - forearm_ref.head
                    current_bone.head = forearm_ref.head + arm_vec * 0.4
                    current_bone.tail = current_bone.head + (forearm_ref.y_axis * length * forearm_ref.length * 3)
                    current_bone.roll = forearm_ref.roll

                if bname == c_wrist_bend_name:
                    current_bone.head = forearm_ref.tail + (forearm_ref.head - forearm_ref.tail) * 0.1
                    current_bone.tail = current_bone.head + ((forearm_ref.tail - forearm_ref.head) * 0.2)
                    current_bone.roll = forearm_ref.roll


    def get_auto_ik_roll():
        arm_ref = get_edit_bone(arm_ref_name + side)
        auto_ik_roll = True
        auto_ik_roll_name = ard.arm_props['auto_ik_roll']        
        if auto_ik_roll_name in arm_ref.keys():#backward-compatibility
            auto_ik_roll = arm_ref[auto_ik_roll_name]
        return auto_ik_roll
        
    
    for side in sides:
        # align FK pre-pole
        prepole = get_edit_bone(prepole_name + side)
        arm_ref = get_edit_bone(arm_ref_name + side)
        forearm_ref = get_edit_bone(forearm_ref_name + side)

        if prepole and arm_ref and forearm_ref:
            # center the prepole in the middle of the chain
            prepole.head[0] = (arm_ref.head[0] + forearm_ref.tail[0]) / 2
            prepole.head[1] = (arm_ref.head[1] + forearm_ref.tail[1]) / 2
            prepole.head[2] = (arm_ref.head[2] + forearm_ref.tail[2]) / 2
            # point toward the elbow
            prepole.tail[0] = arm_ref.tail[0]
            prepole.tail[1] = arm_ref.tail[1]
            prepole.tail[2] = arm_ref.tail[2]

            # align FK pole
            fk_pole = get_edit_bone(fk_pole_name + side)
            # get arm plane normal
            plane_normal = (arm_ref.head - forearm_ref.tail)
            # pole position
            prepole_dir = prepole.tail - prepole.head
            pole_pos = prepole.tail + (prepole_dir).normalized()
            # ortho project onto plane to align with the knee/elbow
            pole_pos = project_point_onto_plane(pole_pos, prepole.tail, plane_normal)
            # make sure to keep a correct distance from the elbow
            custom_dist = 1.0
            if get_edit_bone(hand_ref_name+side).get("ik_pole_distance"):
                custom_dist = get_edit_bone(hand_ref_name+side).get("ik_pole_distance")

            pole_pos = arm_ref.tail + ((pole_pos - arm_ref.tail).normalized() * (arm_ref.tail - arm_ref.head).magnitude * custom_dist)
            
            auto_ik_roll = get_auto_ik_roll()
            if not auto_ik_roll:                
                fac = 1 if scn.arp_retro_axes else -1
                point_on_plane = ((arm_ref.head+forearm_ref.tail)/2) + (arm_ref.x_axis.normalized() * fac * arm_ref.length)
                pole_pos = project_point_onto_plane(pole_pos, point_on_plane, arm_ref.z_axis)
                
            fk_pole.head = pole_pos
            fk_pole.tail = Vector((pole_pos)) + prepole_dir

            # align IK pole
            ik_pole = get_edit_bone(ik_pole_name + side)            
            ik_pole.head = fk_pole.head.copy()            
            ik_pole.tail = [ik_pole.head[0], ik_pole.head[1], ik_pole.head[2] + (0.165 * arm_ref.length * 2)]
            
            # set the IK pole constraints if any
            bpy.ops.object.mode_set(mode='POSE')
            
            pb_ik_pole = get_pose_bone(ik_pole_name + side)
            
            if len(pb_ik_pole.constraints):
                for cns in pb_ik_pole.constraints:
                    if cns.name == "Child Of_local":
                        # try to find the missing target
                        if cns.subtarget == "":
                            _target_name = ard.spine_bones_dict['c_root_master']#"c_root_master.x"
                            if get_pose_bone(_target_name):
                                cns.subtarget = _target_name

                        else:
                            # check the target is valid, if not set to None
                            if not get_pose_bone(cns.subtarget):
                                cns.subtarget = ""

            bpy.ops.object.mode_set(mode='EDIT')

    # set arm and forearm roll
    for side in sides:
        auto_ik_roll = get_auto_ik_roll()        
        
        if not auto_ik_roll:  
            continue
            
        if get_edit_bone(forearm_ref_name + side):
            # calculate forearm Z roll
            init_selection(forearm_ref_name + side)
            
            roll_type = 'NEG_Z'
            if scn.arp_retro_axes:
                roll_type = 'POS_Z'
                
            bpy.ops.armature.calculate_roll(type=roll_type)            
            
            # calculate arm roll
            bpy.ops.object.mode_set(mode='POSE')
            
            bpy.ops.pose.select_all(action='DESELECT')
            
            bpy.ops.object.mode_set(mode='EDIT')
            
            arm_ref = get_edit_bone(arm_ref_name + side)
            arm_ref.select = True
            rig.data.bones.active = get_pose_bone(forearm_ref_name + side).bone
            bpy.ops.armature.calculate_roll(type='ACTIVE')
            
            if side[-2:] == ".r":
                get_edit_bone(forearm_ref_name + side).roll += radians(-180)
                arm_ref.roll += radians(-180)

             
    for side in sides:
        init_selection("null")
        # copy the roll to other bones
        forearm_ref = get_edit_bone(forearm_ref_name + side)
        arm_ref = get_edit_bone(arm_ref_name + side)        
       
        if forearm_ref:
            for bname in forearms:
                roll_copy = forearm_ref.roll                
                current_bone = get_edit_bone(bname + side) 
                if current_bone:                    
                    current_bone.roll = roll_copy

            if "bname" in locals():
                del bname

            for bname in arms:
                roll_copy = arm_ref.roll                
                current_bone = get_edit_bone(bname + side)   
                if current_bone:
                    current_bone.roll = roll_copy

            if "bname" in locals():
                del bname

            # shoulder poles
            # track pole
            shoulder_track_pole = get_edit_bone(shoulder_track_pole_name + side)            
            shoulder_track_pole.head = (arm_ref.head + get_edit_bone(shoulder_ref_name + side).head) / 2
            shoulder_track_pole.head[2] += (0.04 * arm_ref.length * 4)
            dir = forearm_ref.head - shoulder_track_pole.head
            shoulder_track_pole.tail = shoulder_track_pole.head + dir / 4
            shoulder_track_pole.roll = arm_ref.roll
            
            # pole
            shoulder_pole = get_edit_bone(shoulder_pole_name + side)
            shoulder_pole.head = arm_ref.head + arm_ref.z_axis * (-0.1 * arm_ref.length * 8)
            shoulder_pole.tail = shoulder_pole.head + arm_ref.y_axis * (0.1 * arm_ref.length * 4)
            
           
    # stretch controller, pin controller
    for side in sides:
        arm_ref = get_edit_bone(arm_ref_name + side)
        forearm_ref = get_edit_bone(forearm_ref_name + side)
        c_stretch_arm = get_edit_bone(c_stretch_arm_name + side)
        stretch_arm_pin = get_edit_bone(arm_pin_name + side)

        if arm_ref:
            # stretch controller
            if c_stretch_arm:
                if scn.arp_retro_stretch_pin:# backward-compatibility
                    dir = c_stretch_arm.tail - c_stretch_arm.head
                    c_stretch_arm.head = arm_ref.tail.copy()
                    c_stretch_arm.tail = c_stretch_arm.head + dir
                else:
                    dir = (arm_ref.x_axis + forearm_ref.x_axis) * 0.5
                    if side.endswith('.r'):
                        dir *= -1
                        
                    b_len = (arm_ref.tail - arm_ref.head).magnitude
                    c_stretch_arm.head = arm_ref.tail.copy()
                    c_stretch_arm.tail = c_stretch_arm.head + (dir.normalized() * b_len * 0.65)
                    align_bone_z_axis(c_stretch_arm, -arm_ref.y_axis)

                # pin controller                
                if stretch_arm_pin:
                    if scn.arp_retro_stretch_pin:# backward-compatibility
                        stretch_arm_pin.head = arm_ref.tail.copy()
                        stretch_arm_pin.tail = stretch_arm_pin.head + (dir * 0.05)
                    else:
                        stretch_arm_pin.head = c_stretch_arm.head.copy()
                        stretch_arm_pin.tail = stretch_arm_pin.head + ((c_stretch_arm.tail-c_stretch_arm.head) * 0.6)
                        align_bone_z_axis(stretch_arm_pin, c_stretch_arm.z_axis)
                        
                    
    if not scn.arp_retro_stretch_pin:
        bpy.ops.object.mode_set(mode='POSE')
        
        for side in sides:
            stretch_arm_pin = get_pose_bone(arm_pin_name + side)
            set_custom_shape_scale(stretch_arm_pin, 0.8)
            
        bpy.ops.object.mode_set(mode='EDIT')
        
    
    arm_twist_dict = {}

    for side in sides:
        hand_ref = get_edit_bone(hand_ref_name + side)
        hand_rot_twist = get_edit_bone(hand_rot_twist_name + side)
        forearm_ref = get_edit_bone(forearm_ref_name + side)

        # align hand_rot_twist
        if hand_ref and hand_rot_twist:
            # mult by hand_ref.length to keep proportional when scaling the armature object and applying scale
            hand_rot_twist.head = hand_ref.head + (hand_ref.y_axis * 0.02 * hand_ref.length * 15.0) + (hand_ref.z_axis * 0.04 * hand_ref.length * 15.0)
            hand_rot_twist.tail = hand_rot_twist.head.copy() + (forearm_ref.y_axis * 0.02 * hand_ref.length * 15.0)

            # align hands
            hands = [hand_name+side, c_hand_ik_name+side, c_hand_fk_name+side, hand_scale_fix_name+side]            
            
            for bname in hands:
                current_hand = get_edit_bone(bname)                
                copy_bone_transforms(hand_ref, current_hand)
            

            # Align hand_rot_twist and forearm_twist rolls to the hand roll
            print("  Align roll ["+side+"]")
         
            align_bone_x_axis(hand_rot_twist, hand_ref.x_axis)
            if scn.arp_retro_axes:# backward-compatibility
                hand_rot_twist.roll += radians(180)            
          
            forearm_twist = get_edit_bone(forearm_twist_name + side)            
            align_bone_x_axis(forearm_twist, hand_ref.x_axis)
            if scn.arp_retro_axes:# backward-compatibility
                forearm_twist.roll += radians(180)
            

        # setup twist bones
        # get arm twist amount
        twist_bones_amount = 1
        armb = get_edit_bone(arm_ref_name+side)
        if rig.arp_secondary_type != "BENDY_BONES":
            if len(armb.keys()):
                if "twist_bones_amount" in armb.keys():# backward-compatibility
                    twist_bones_amount = armb["twist_bones_amount"]

        arm_twist_dict[side] = twist_bones_amount

        # set twist function
        set_arm_twist(twist_bones_amount, side)
    
    
    bpy.ops.object.mode_set(mode='POSE')
    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.mode_set(mode='POSE')
    

    for side in sides:        
        # set arm FK lock constraint
        c_arm_fk = get_pose_bone(c_arm_fk_name+side)
        armlock_cns = c_arm_fk.constraints.get('ArmLock')
        if armlock_cns:
            c_shoulder = get_pose_bone(c_shoulder_name+side)
            for i, tar in enumerate(armlock_cns.targets):
                tar.subtarget = c_shoulder.parent.name if i == 0 else 'c_traj'
        
        # set default IK-FK switch value
        c_hand_ik = get_pose_bone(c_hand_ik_name+side)        
        default_IKFK = arms_ikfk_default_dict[side]    
        default_val = 0.0 if default_IKFK == 'IK' else 1.0
        set_prop_setting(c_hand_ik, 'ik_fk_switch', 'default', default_val)        
        arm_ik_pb = get_pose_bone(arm_ik_name + side)
        forearm_ik_pb = get_pose_bone(forearm_ik_name + side)

        if arm_ik_pb and forearm_ik_pb:
            arm_ik_length = arm_ik_pb.length
            forearm_ik_length = forearm_ik_pb.length

            # set arm IK stretch value
            if arm_ik_length < forearm_ik_length:
                arm_ik_pb.ik_stretch = (arm_ik_length ** (1 / 3)) / (forearm_ik_length ** (1 / 3))
                forearm_ik_pb.ik_stretch = 1.0
            else:
                arm_ik_pb.ik_stretch = 1.0
                forearm_ik_pb.ik_stretch = (forearm_ik_length ** (1 / 3)) / (arm_ik_length ** (1 / 3))
            

            def remove_twist_based_bendy(side):
                bpy.ops.object.mode_set(mode='EDIT')

                # remove bendy bones
                forearm_bendy_n = forearm_bendy_name + side
                arm_bendy_n = arm_bendy_name + side
                forearm_bendy = get_edit_bone(forearm_bendy_n)
                arm_bendy = get_edit_bone(arm_bendy_n)
                if forearm_bendy:
                    delete_edit_bone(forearm_bendy)
                if arm_bendy:
                    delete_edit_bone(arm_bendy)

            def remove_twist_based_segments(side):
                bpy.ops.object.mode_set(mode='EDIT')

                for idx in range(1, 33):
                    for arm_limb in ["forearm", "arm"]:
                        bone_segment = get_edit_bone(arm_limb + "_" + "segment_" + str(idx) + side)
                        if bone_segment:
                            delete_edit_bone(bone_segment)

            def remove_twist_based_constraints(side):
                bpy.ops.object.mode_set(mode='POSE')

                for arm_limb in ["forearm", "arm"]:
                    for idx in range(1, 33):
                        twist_idx = "_" + str(idx)
                        if idx == 1:
                            twist_idx = ""
                        # twist constraints
                        twist_pbone = get_pose_bone(arm_limb + "_twist" + twist_idx + side)
                        if twist_pbone == None:
                            continue

                        cns_loc = twist_pbone.constraints.get("Copy Location_wrap")
                        if cns_loc:
                            twist_pbone.constraints.remove(cns_loc)

                        cns_damped = twist_pbone.constraints.get("Damped Track_wrap")
                        if cns_damped:
                            twist_pbone.constraints.remove(cns_damped)

            def remove_twist_based_str(side):
                bpy.ops.object.mode_set(mode='EDIT')

                for arm_limb in ["forearm", "arm"]:
                    arm_str_offset = get_edit_bone(arm_limb + "_str_offset" + side)
                    if arm_str_offset:
                        delete_edit_bone(arm_str_offset)

                    arm_twt_offset = get_edit_bone(arm_limb + "_twt_offset" + side)
                    if arm_twt_offset:
                        delete_edit_bone(arm_twt_offset)


            print("  Set secondary controllers ["+side+"]")
            drivers_list = rig.animation_data.drivers

            # generate the twist bones list
            twist_bones_amount = arm_twist_dict[side]
            twist_bones_list = []

            for arm_type in ['forearm', 'arm']:
                for twist_idx in range(1, twist_bones_amount + 1):
                    str_idx = '_' + str(twist_idx)
                    if twist_idx == 1:
                        str_idx = ''# the first twist bone has no id by convention
                    twist_name = arm_type + '_twist' + str_idx + side
                    twist_bones_list.append(twist_name)
                    
                # add the stretch bone to the list
                twist_bones_list.append(arm_type + '_stretch' + side)
                
            
            # 1.Bendy bones
            if rig.arp_secondary_type == "BENDY_BONES":

                bpy.ops.object.mode_set(mode='EDIT')

                secondary_list_remove = [c_elbow_bend_name]
                secondary_list = {c_shoulder_bend_name:arm_stretch_name, c_arm_bend_name:arm_stretch_name, c_forearm_bend_name: forearm_stretch_name, c_wrist_bend_name: forearm_stretch_name}
                created_bones = []

                # Remove secondary controllers
                for bn in secondary_list_remove:
                    ebn = get_edit_bone(bn+side)
                    if ebn:
                        delete_edit_bone(ebn)

                # proxy
                for bn in secondary_list_remove:
                    ebn_proxy = get_edit_bone(bn+'_proxy'+side)
                    if ebn_proxy:
                        delete_edit_bone(ebn_proxy)


                # create missing secondary controllers
                for bn in secondary_list:
                    eb = get_edit_bone(bn+side)
                    if eb == None:
                        eb = rig.data.edit_bones.new(bn+side)
                        # set layer
                        set_bone_layer(eb, 1)
                        created_bones.append(eb.name)

                    # set deform
                    eb.use_deform = False
                    # set parents
                    eb.parent = get_edit_bone(secondary_list[bn]+side)
                    # set visibility
                    eb.hide = False

                align_arm_bend_bones(side)


                arm_stretch = get_edit_bone(arm_stretch_name+side)
                forearm_stretch = get_edit_bone(forearm_stretch_name+side)

                # get bbones ease out driven state
                arm_ref = get_edit_bone(arm_ref_name+side)
                arm_bbones_ease_out = arm_ref.get("arp_bbones_ease_out")
                set_ease_out_driver = True
                if arm_bbones_ease_out != None:
                    set_ease_out_driver = arm_bbones_ease_out

                # get bones lengths
                arm_length = arm_stretch.length
                forearm_length = forearm_stretch.length

                # enable stretch deform
                arm_stretch.use_deform = True
                forearm_stretch.use_deform = True

                bpy.ops.object.mode_set(mode='POSE')

                for bn in created_bones:
                    pbn = get_pose_bone(bn)
                    # set rot mode
                    pbn.rotation_mode = "XYZ"
                    # set custom shape
                    set_bone_custom_shape(pbn, "cs_torus_02")
                    # set bone group
                    pbn.bone_group = rig.pose.bone_groups.get('body'+side[-2:])
                    # set visibility
                    pbn.bone.hide = False


                # constraints
                cns = get_pose_bone(arm_stretch_name + side).constraints.get("Copy Location")
                if cns:  # backward-compatibility
                    cns.head_tail = 0.0

                # disable twist deform and rig_add bend bones deform
                get_pose_bone(arm_twist_offset_name + side).bone.use_deform = False
                get_pose_bone(forearm_twist_name + side).bone.use_deform = False


                # hide c_arm_twist_offset
                arm_twist_offset = get_data_bone(arm_twist_offset_name + side)

                if arm_twist_offset:
                    set_bone_layer(arm_twist_offset, 8)

                    # proxy
                arm_twist_offset_proxy = get_pose_bone(arm_twist_offset_name + '_proxy' + side)
                if arm_twist_offset_proxy:
                    set_bone_layer(arm_twist_offset_proxy.bone, 8)


                    # unhide the used
                unhidden_secondary = [c_shoulder_bend_name, c_arm_bend_name, c_forearm_bend_name, c_wrist_bend_name]
                for bn in unhidden_secondary:
                    bnp = get_pose_bone(bn + side)
                    if bnp == None:
                        continue
                    bnp.bone.hide = False

                    # proxy
                for bn in unhidden_secondary:
                    bnp_proxy = get_pose_bone(bn + '_proxy' + side)
                    if bnp_proxy == None:
                        continue
                    bnp_proxy.bone.hide = False

                    # custom handles
                arm_stretch_pb = get_pose_bone(arm_stretch_name + side)
                forearm_stretch_pb = get_pose_bone(forearm_stretch_name + side)
                shoulder_pb = get_pose_bone(shoulder_name + side)
                hand_rot_pb = get_pose_bone(hand_rot_twist_name + side)

                arm_stretch_pb.bone.bbone_handle_type_start = "ABSOLUTE"
                arm_stretch_pb.bone.bbone_handle_type_end = "ABSOLUTE"

                forearm_stretch_pb.bone.bbone_handle_type_start = "AUTO"  # Absolute leads to slightly bend the first bbones, set it to Automatic instead
                forearm_stretch_pb.bone.bbone_handle_type_end = "ABSOLUTE"

                arm_stretch_pb.bone.bbone_custom_handle_start = shoulder_pb.bone
                arm_stretch_pb.bone.bbone_custom_handle_end = forearm_stretch_pb.bone

                forearm_stretch_pb.bone.bbone_custom_handle_start = arm_stretch_pb.bone
                if set_ease_out_driver:
                    forearm_stretch_pb.bone.bbone_custom_handle_end = hand_rot_pb.bone

                # Set the drivers
                # arm bones
                set_secondary_drivers(drivers_list, [arm_stretch_name, c_shoulder_bend_name, c_arm_bend_name], side, arm_length)

                # forearm bones
                set_secondary_drivers(drivers_list, [forearm_stretch_name, c_forearm_bend_name, c_wrist_bend_name], side, forearm_length, enable_ease_out_dr=set_ease_out_driver)

                # Remove any unwanted bones from other modes controllers
                # twist bones amount is automatically set to 1 for bendy bones, then iterate over the full range 1-33
                twist_bones_list = []
                for arm_type in ['forearm', 'arm']:
                    for twist_idx in range(1, 33):
                        str_idx = '_' + str(twist_idx)
                        if twist_idx == 1:
                            str_idx = ''  # the first twist bone has no id by convention
                        twist_bones_list.append(arm_type + '_twist' + str_idx + side)

                    # add the stretch bone to the list
                    twist_bones_list.append(arm_type + '_stretch' + side)

                bpy.ops.object.mode_set(mode='EDIT')

                for bname in twist_bones_list:
                    b_twist = get_edit_bone(bname)
                    c_twist_name = 'c_' + bname
                    c_twist = get_edit_bone(c_twist_name)
                    # remove
                    if c_twist:
                        delete_edit_bone(c_twist)

                remove_twist_based_constraints(side)
                remove_twist_based_segments(side)
                remove_twist_based_bendy(side)
                remove_twist_based_str(side)

                bpy.ops.object.mode_set(mode='POSE')
            
            
            # 2.Additive
            elif rig.arp_secondary_type == "ADDITIVE":

                bpy.ops.object.mode_set(mode='EDIT')

                secondary_list = {c_shoulder_bend_name: arm_twist_name, c_arm_bend_name: arm_stretch_name, c_elbow_bend_name: arm_stretch_name, c_forearm_bend_name: forearm_stretch_name, c_wrist_bend_name: forearm_twist_name}
                created_bones = []

                # create missing secondary controllers
                for bn in secondary_list:
                    eb = get_edit_bone(bn+side)
                    if eb == None:
                        eb = rig.data.edit_bones.new(bn+side)
                        # set layer
                        set_bone_layer(eb, 1)
                        created_bones.append(eb.name)

                    # set deform
                    eb.use_deform = False
                    # set parents
                    eb.parent = get_edit_bone(secondary_list[bn]+side)
                    # set visibility
                    eb.hide = False

                align_arm_bend_bones(side)

                bpy.ops.object.mode_set(mode='POSE')

                for bn in created_bones:
                    pbn = get_pose_bone(bn)
                    # set rot mode
                    pbn.rotation_mode = "XYZ"
                    # set custom shape
                    set_bone_custom_shape(pbn, "cs_torus_02")
                    # set bone group
                    pbn.bone_group = rig.pose.bone_groups.get('body'+side[-2:])
                    # set visibility
                    pbn.bone.hide = False


                # custom handles
                get_pose_bone(arm_stretch_name + side).bone.bbone_handle_type_start = 'AUTO'
                get_pose_bone(arm_stretch_name + side).bone.bbone_handle_type_end = 'AUTO'
                get_pose_bone(forearm_stretch_name + side).bone.bbone_handle_type_start = 'AUTO'
                get_pose_bone(forearm_stretch_name + side).bone.bbone_handle_type_end = 'AUTO'

                # constraints
                cns = get_pose_bone(arm_stretch_name + side).constraints.get("Copy Location")
                if cns:
                    cns.head_tail = 1.0

                # Set twist deform, unhide, and rig_add bend deform
                arm_twist_offset = get_pose_bone(arm_twist_offset_name + side)
                forearm_twist = get_pose_bone(forearm_twist_name + side)

                if arm_twist_offset:
                    set_bone_layer(arm_twist_offset.bone, 0)
                    arm_twist_offset.bone.hide = False# backward-compatibility fix

                    if arm_twist_offset.bone.layers[22] == False:
                        arm_twist_offset.bone.use_deform = True
                        forearm_twist.bone.use_deform = True

                # proxy
                arm_twist_offset_proxy = get_pose_bone(arm_twist_offset_name+'_proxy'+side)

                if arm_twist_offset_proxy:
                    set_bone_layer(arm_twist_offset_proxy.bone, 0)
                    arm_twist_offset_proxy.bone.hide = False# backward-compatibility fix


                _rig_add = get_rig_add(rig)

                for add_bone in ard.arm_bones_rig_add:
                    rig_add_pbone = _rig_add.pose.bones.get(add_bone + side)
                    if rig_add_pbone:
                        rig_add_pbone.bone.use_deform = True


                # Set twist controllers
                # if Additive mode, remove any additional twist controllers
                bpy.ops.object.mode_set(mode='EDIT')
                for bname in twist_bones_list:
                    b_twist = get_edit_bone(bname)
                    c_twist_name = 'c_' + bname
                    c_twist = get_edit_bone(c_twist_name)
                    # remove
                    if c_twist:
                        delete_edit_bone(c_twist)
                    # enable base twist bone deform
                    b_twist.use_deform = True

                remove_twist_based_constraints(side)
                remove_twist_based_segments(side)
                remove_twist_based_bendy(side)
                remove_twist_based_str(side)

                bpy.ops.object.mode_set(mode='POSE')
            
            
            # 3.Twist modes
            elif rig.arp_secondary_type == "TWIST_BASED":
                secondary_list_remove = [c_elbow_bend_name]
                secondary_list = {c_shoulder_bend_name: arm_stretch_name, c_arm_bend_name: arm_stretch_name, c_forearm_bend_name: forearm_stretch_name, c_wrist_bend_name: forearm_stretch_name}
                created_bones = []

                bpy.ops.object.mode_set(mode='EDIT')

                # Remove secondary controllers
                for bn in secondary_list_remove:
                    ebn = get_edit_bone(bn+side)
                    if ebn:
                        delete_edit_bone(ebn)

                #   proxy
                for bn in secondary_list_remove:
                    ebn_proxy = get_edit_bone(bn+'_proxy'+side)
                    if ebn_proxy:
                        delete_edit_bone(ebn_proxy)


                # create missing secondary controllers
                for bn in secondary_list:
                    eb = get_edit_bone(bn+side)
                    if eb == None:
                        eb = rig.data.edit_bones.new(bn+side)
                        # set layer
                        set_bone_layer(eb, 1)
                        created_bones.append(eb.name)

                    # set deform
                    eb.use_deform = False
                    # set parents
                    eb.parent = get_edit_bone(secondary_list[bn]+side)
                    # set visibility
                    eb.hide = False
                
                align_arm_bend_bones(side)
                
                bpy.ops.object.mode_set(mode='POSE')
                
                for bn in created_bones:
                    pbn = get_pose_bone(bn)
                    # set rot mode
                    pbn.rotation_mode = "XYZ"
                    # set custom shape
                    set_bone_custom_shape(pbn, "cs_torus_02")
                    # set bone group
                    pbn.bone_group = rig.pose.bone_groups.get('body'+side[-2:])
                    # set visibility
                    pbn.bone.hide = False

                # set custom handles
                arm_stretch_pb = get_pose_bone(arm_stretch_name + side)
                forearm_stretch_pb = get_pose_bone(forearm_stretch_name + side)

                arm_stretch_pb.bone.bbone_handle_type_start = 'AUTO'
                arm_stretch_pb.bone.bbone_handle_type_end = 'AUTO'
                forearm_stretch_pb.bone.bbone_handle_type_start = 'AUTO'
                forearm_stretch_pb.bone.bbone_handle_type_end = 'AUTO'

                # set constraints
                cns = get_pose_bone(arm_stretch_name + side).constraints.get("Copy Location")
                if cns:
                    cns.head_tail = 1.0

                # Set twist deform and bend deform
                # in Twist Based mode, additive secondary controllers don't deform
                arm_twist_offset = get_pose_bone(arm_twist_offset_name + side)

                if arm_twist_offset.bone.layers[22] == False:# if not disabled
                    arm_twist_offset.bone.use_deform = False# c_arm_twist_offset is replaced by the first twist bone deformation
                    get_pose_bone(forearm_twist_name+side).bone.use_deform = True
                
                
                # Set visibility
                # Hide c_arm_twist_offset in layer
                arm_twist_offset = get_pose_bone(arm_twist_offset_name+side)
                
                if arm_twist_offset:
                    set_bone_layer(arm_twist_offset.bone, 8)
                
                #   proxy
                arm_twist_offset_proxy = get_pose_bone(arm_twist_offset_name+'_proxy'+side)
                if arm_twist_offset_proxy:
                    set_bone_layer(arm_twist_offset_proxy.bone, 8)

                bpy.ops.object.mode_set(mode='EDIT')

                # Set Twist Controllers
                #   delete unwanted controllers bones
                for idx in range(twist_bones_amount + 1, 33):
                    for blimb in ['arm', 'forearm']:
                        c_twist_to_del = get_edit_bone("c_" + blimb + "_twist_" + str(idx) + side)
                        if c_twist_to_del:
                            delete_edit_bone(c_twist_to_del)

                #   add new offset bones
                for arm in ['arm', 'forearm']:
                    # create an offset bone for the arms stretch bone, to preserve the stretch bone rotation when curving the twist bones
                    str_offset_name = arm + "_str_offset" + side
                    arm_str_offset = get_edit_bone(str_offset_name)
                    if arm_str_offset == None:
                        arm_str_offset = rig.data.edit_bones.new(str_offset_name)
                    arm_stretch = get_edit_bone(arm + "_stretch" + side)
                    # set coords
                    arm_str_offset.head, arm_str_offset.tail, arm_str_offset.roll = arm_stretch.head.copy(), arm_stretch.tail.copy(), arm_stretch.roll
                    # set parent
                    arm_str_offset.parent = arm_stretch
                    # set layers
                    set_bone_layer(arm_str_offset, 9)
                    # set deform
                    arm_str_offset.use_deform = False
                    # replace it in the list
                    index_in_list = twist_bones_list.index(arm + "_stretch" + side)
                    twist_bones_list.pop(index_in_list)
                    twist_bones_list.insert(index_in_list, arm + "_str_offset" + side)

                    # create an offset bone for the arm_twist bone, to preserve the stretch bone rotation when curving the twist bones
                    if arm == "arm":
                        twist_offset_name = arm + "_twt_offset" + side
                        twist_offset = get_edit_bone(twist_offset_name)
                        if twist_offset == None:
                            twist_offset = rig.data.edit_bones.new(twist_offset_name)
                        arm_twist = get_edit_bone(arm + "_twist" + side)
                        arm_twist.use_deform = False
                        # set coords
                        twist_offset.head, twist_offset.tail, twist_offset.roll = arm_twist.head.copy(), arm_twist.tail.copy(), arm_twist.roll
                        # set parent
                        twist_offset.parent = arm_twist
                        # set layers
                        set_bone_layer(twist_offset, 9)
                        # set deform
                        twist_offset.use_deform = False
                        # replace it in the list
                        index_in_list = twist_bones_list.index(arm + "_twist" + side)
                        twist_bones_list.pop(index_in_list)
                        twist_bones_list.insert(index_in_list, arm + "_twt_offset" + side)

                # create the twist controllers
                c_twist_bones_names = []
                
                for bname in twist_bones_list:                   
                    b_twist = get_edit_bone(bname)
                    base_stretch = None
                    c_twist_name = 'c_' + bname
                    
                    if "arm_str_offset" in bname:# exception, stretch offset case
                        base_stretch = get_edit_bone(bname.replace("_str_offset", "_stretch"))
                        c_twist_name = c_twist_name.replace("_str_offset", "_stretch")
                        
                    if "arm_twt_offset" in bname:# exception, twist offset case
                        c_twist_name = c_twist_name.replace("_twt_offset", "_twist")

                    c_twist = get_edit_bone(c_twist_name)
                    # create the bone
                    if c_twist == None:
                        c_twist = rig.data.edit_bones.new(c_twist_name)

                    # set coords
                    copy_bone_transforms(b_twist, c_twist)
                    # disable base twist bones deform
                    b_twist.use_deform = False
                   
                    # enable c_twist bone deform
                    c_twist.use_deform = True
                    # set parent
                    c_twist.parent = b_twist
                    # set layers
                    set_bone_layer(c_twist, 1)
                    # the base stretch bone must not deform
                    if base_stretch:
                        base_stretch.use_deform = False

                    c_twist_bones_names.append(c_twist_name)                    
                    
                    
                bpy.ops.object.mode_set(mode='POSE')
                
                
                for c_twist_name in c_twist_bones_names:
                    c_twist_pbone = get_pose_bone(c_twist_name)
                    # set rotation mode
                    c_twist_pbone.rotation_mode = "XYZ"
                    # set bone shape
                    twist_shape = get_object("cs_twist_shape")
                    if twist_shape == None:
                        append_from_arp(nodes=["cs_twist_shape"], type="object")

                    set_custom_shape = True

                    if c_twist_pbone.custom_shape != None:
                        if c_twist_pbone.custom_shape.name.startswith("cs_user_"):
                            set_custom_shape = False

                    if set_custom_shape:
                        c_twist_pbone.custom_shape = get_object("cs_twist_shape")

                        if twist_bones_amount < 7:#backward-compatibility, twist_bones_amount was limited to 6
                            set_custom_shape_scale(c_twist_pbone, (1 / (10 - twist_bones_amount)) * 4)
                        else:
                            set_custom_shape_scale(c_twist_pbone, twist_bones_amount / 6)

                    # set bone group
                    if c_twist_pbone.bone_group == None:
                        c_twist_pbone.bone_group = rig.pose.bone_groups.get('body' + side[-2:])


                # Add a bendy bone for easy curvature control of the twist bones + add segment bones wrapped to it
                for arm in ['arm', 'forearm']:

                    bpy.ops.object.mode_set(mode='EDIT')

                    # Bendy Bone
                    bendy_bone_name = arm + "_bendy" + side
                    bendy_bone = get_edit_bone(bendy_bone_name)
                    if bendy_bone == None:
                        bendy_bone = rig.data.edit_bones.new(bendy_bone_name)
                    arm_ebone = get_edit_bone(arm + side)
                    # set coords
                    bendy_bone.head, bendy_bone.tail, bendy_bone.roll = arm_ebone.head.copy(), arm_ebone.tail.copy(), arm_ebone.roll
                    bendy_bone.bbone_segments = twist_bones_amount + 1
                    arm_length = bendy_bone.length
                    # set parent
                    bendy_bone.parent = get_edit_bone(get_first_master_controller())
                    # set layers
                    set_bone_layer(bendy_bone, 9)
                    # set deformation
                    bendy_bone.use_deform = False

                    # bendy bone: set constraints
                    bpy.ops.object.mode_set(mode='POSE')
                    bendy_bone_pbone = get_pose_bone(bendy_bone_name)

                    cns_loc = bendy_bone_pbone.constraints.get("Copy Location")
                    if cns_loc == None:
                        cns_loc = bendy_bone_pbone.constraints.new("COPY_LOCATION")
                    cns_loc.name = "Copy Location"
                    cns_loc.target = rig
                    if arm == "forearm":
                        cns_loc.subtarget = c_stretch_arm_name + side
                    elif arm == "arm":
                        cns_loc.subtarget = arm_name + side

                    cns_rot = bendy_bone_pbone.constraints.get("Copy Rotation")
                    if cns_rot == None:
                        cns_rot = bendy_bone_pbone.constraints.new("COPY_ROTATION")
                    cns_rot.name = "Copy Rotation"
                    cns_rot.target = rig
                    cns_rot.subtarget = arm + side

                    cns_stretch = bendy_bone_pbone.constraints.get("Stretch To")
                    if cns_stretch == None:
                        cns_stretch = bendy_bone_pbone.constraints.new("STRETCH_TO")
                    cns_stretch.name = "Stretch To"
                    cns_stretch.target = rig
                    if arm == "forearm":
                        cns_stretch.subtarget = hand_name + side
                    elif arm == "arm":
                        cns_stretch.subtarget = c_stretch_arm_name + side
                    cns_stretch.volume = "NO_VOLUME"

                    # bendy bone: set drivers
                    drivers_list = rig.animation_data.drivers

                    if arm == "forearm":
                        set_secondary_drivers(drivers_list, [forearm_bendy_name, c_forearm_bend_name, c_wrist_bend_name], side, arm_length)
                    elif arm == "arm":
                        set_secondary_drivers(drivers_list, [arm_bendy_name, c_shoulder_bend_name, c_arm_bend_name], side, arm_length)

                    #   Bones Segments
                    bpy.ops.object.mode_set(mode='EDIT')

                    # delete unwanted bones segments
                    for idx in range(twist_bones_amount + 1, 33):
                        bone_segment = get_edit_bone(arm + "_" + "segment_" + str(idx) + side)

                        # the arm bone has an extra segment, keep it
                        if arm == "arm" and idx == twist_bones_amount + 1:
                            continue

                        if bone_segment:
                            delete_edit_bone(bone_segment)

                            
                    bpy.ops.object.mode_set(mode='EDIT')
                    
                    
                    # add bones segments
                    bone_segments_names = {}
                    
                    for idx in range(1, twist_bones_amount + 1):                        

                        bone_segment_name = arm + "_segment_" + str(idx) + side
                        bone_segment = get_edit_bone(bone_segment_name)
                        if bone_segment == None:
                            bone_segment = rig.data.edit_bones.new(bone_segment_name)
                            
                        # set coords
                        twist_bone_name = arm + "_twist_" + str(idx) + side
                        if idx == 1:
                            twist_bone_name = arm + "_twist" + side
                        twist_bone = get_edit_bone(twist_bone_name)
                        bone_segment.head = twist_bone.head.copy()
                        bone_segment.tail = bone_segment.head + (-twist_bone.z_axis.normalized() * (twist_bone.tail - twist_bone.head).magnitude)
                        bone_segment.roll = 0.0
                        # parent
                        bone_segment.parent = get_edit_bone(bendy_bone_name)
                        # set layers
                        set_bone_layer(bone_segment, 11)
                        # set deform
                        bone_segment.use_deform = False
                        
                        bone_segments_names[bone_segment_name] = idx
                        
                        if arm == "arm" and idx == twist_bones_amount:# an extra segment bone must be added for the last twist bone of the arm
                            bone_segment_name = arm + "_segment_" + str(idx + 1) + side
                            bone_segment = get_edit_bone(bone_segment_name)
                            if bone_segment == None:
                                bone_segment = rig.data.edit_bones.new(bone_segment_name)
                                
                            # set coords
                            twist_bone = get_edit_bone(twist_bone_name)
                            bone_segment.head = twist_bone.tail.copy()
                            bone_segment.tail = bone_segment.head + (-twist_bone.z_axis.normalized() * (twist_bone.tail - twist_bone.head).magnitude)
                            bone_segment.roll = 0.0
                            # parent
                            bone_segment.parent = get_edit_bone(bendy_bone_name)
                            # set layers
                            set_bone_layer(bone_segment, 11)
                            # set deform
                            bone_segment.use_deform = False
                            
                            #bone_segments_names[bone_segment_name] = idx+1
                            
                    bpy.ops.object.mode_set(mode='POSE')
                    
                    # set constraints
                    for bone_segment_name in bone_segments_names:                    
                        idx = bone_segments_names[bone_segment_name]
                
                        bone_segment_pbone = get_pose_bone(bone_segment_name)
                        cns = bone_segment_pbone.constraints.get("Copy Location")
                        if cns == None:
                            cns = bone_segment_pbone.constraints.new("COPY_LOCATION")
                        cns.name = "Copy Location"
                        cns.target = rig
                        cns.subtarget = bendy_bone_name
                        if arm == "arm":
                            cns.head_tail = (1 / (twist_bones_amount + 1)) * (idx - 1)
                        elif arm == "forearm":
                            cns.head_tail = 1 - (idx / (twist_bones_amount + 1))

                        cns.use_bbone_shape = True
                        
                        if arm == "arm" and idx == twist_bones_amount:# extra segment for the last twist bone of the arm
                            bone_segment_name = arm + "_segment_" + str(idx + 1) + side
                            bone_segment_pbone = get_pose_bone(bone_segment_name)
                            cns = bone_segment_pbone.constraints.get("Copy Location")
                            if cns == None:
                                cns = bone_segment_pbone.constraints.new("COPY_LOCATION")
                            cns.name = "Copy Location"
                            cns.target = rig
                            cns.subtarget = bendy_bone_name
                            cns.head_tail = (1 / (twist_bones_amount + 1)) * (idx)
                            cns.use_bbone_shape = True                            
                        
                    # wrap twist bones on bone segments
                    for idx in range(1, twist_bones_amount + 1):
                        twist_idx = "_" + str(idx)
                        if idx == 1:
                            twist_idx = ""

                        twist_pbone = get_pose_bone(arm + "_twist" + twist_idx + side)

                        # add loc constraint
                        cns_loc = twist_pbone.constraints.get("Copy Location_wrap")
                        if cns_loc == None:
                            cns_loc = twist_pbone.constraints.new("COPY_LOCATION")
                        cns_loc.name = "Copy Location_wrap"
                        cns_loc.target = rig
                        cns_loc.subtarget = arm + "_segment_" + str(idx) + side

                        if arm == "forearm":
                            # add damped track constraints
                            if idx != 1:  # the first twist bone has already a Stretch To constraint to the hand
                                cns_damped = twist_pbone.constraints.get("Damped Track_wrap")
                                if cns_damped == None:
                                    cns_damped = twist_pbone.constraints.new("DAMPED_TRACK")
                                cns_damped.name = "Damped Track_wrap"
                                cns_damped.target = rig
                                cns_damped.subtarget = "forearm" + "_segment_" + str(idx - 1) + side
                            else:
                                # the StretchTo constraint must be last in the stack, delete it then add it
                                stretch_cns = twist_pbone.constraints.get("Stretch To")
                                if stretch_cns:
                                    twist_pbone.constraints.remove(stretch_cns)
                                stretch_cns = twist_pbone.constraints.new("STRETCH_TO")
                                stretch_cns.name = "Stretch To"
                                stretch_cns.target = rig
                                stretch_cns.subtarget = hand_name + side
                                stretch_cns.volume = "NO_VOLUME"

                            # at last, setup the stretch bone constraint
                            # must point toward the last bone segment
                            if idx == twist_bones_amount:
                                c_stretch = get_pose_bone("forearm_str_offset" + side)
                                cns_damped = c_stretch.constraints.get("Damped Track_wrap")
                                if cns_damped == None:
                                    cns_damped = c_stretch.constraints.new("DAMPED_TRACK")
                                cns_damped.name = "Damped Track_wrap"
                                cns_damped.target = rig
                                cns_damped.subtarget = "forearm_segment_" + str(idx) + side

                        elif arm == "arm":
                            if idx == 1:
                                arm_twt_offset = get_pose_bone("arm_twt_offset" + side)
                                # damped track
                                cns_damp = arm_twt_offset.constraints.get("Damped Track_wrap")
                                if cns_damp == None:
                                    cns_damp = arm_twt_offset.constraints.new("DAMPED_TRACK")
                                cns_damp.name = "Damped Track_wrap"
                                cns_damp.target = rig
                                cns_damp.subtarget = "arm_segment_" + str(idx + 1) + side

                            # add damped track
                            else:
                                cns_damped = twist_pbone.constraints.get("Damped Track_wrap")
                                if cns_damped == None:
                                    cns_damped = twist_pbone.constraints.new("DAMPED_TRACK")
                                cns_damped.name = "Damped Track_wrap"
                                cns_damped.target = rig
                                cns_damped.subtarget = "arm_segment_" + str(idx + 1) + side

                            if idx == twist_bones_amount:
                                # at last add constraints to the stretch bone of the arm
                                c_stretch = get_pose_bone("arm_str_offset" + side)
                                # loc
                                cns_loc = c_stretch.constraints.get("Copy Location_wrap")
                                if cns_loc == None:
                                    cns_loc = c_stretch.constraints.new("COPY_LOCATION")
                                cns_loc.name = "Copy Location_wrap"
                                cns_loc.target = rig
                                cns_loc.subtarget = "arm_segment_" + str(idx + 1) + side

                                # damped track
                                cns_damped = c_stretch.constraints.get("Damped Track_wrap")
                                if cns_damped == None:
                                    cns_damped = c_stretch.constraints.new("DAMPED_TRACK")
                                cns_damped.name = "Damped Track_wrap"
                                cns_damped.target = rig
                                cns_damped.subtarget = c_stretch_arm_name + side
                                

                bpy.ops.object.mode_set(mode='POSE')


            # 4. None mode
            elif rig.arp_secondary_type == "NONE":

                bpy.ops.object.mode_set(mode='POSE')

                # set bbones custom handles
                get_pose_bone(arm_stretch_name + side).bone.bbone_handle_type_start = 'AUTO'
                get_pose_bone(arm_stretch_name + side).bone.bbone_handle_type_end = 'AUTO'
                get_pose_bone(forearm_stretch_name + side).bone.bbone_handle_type_start = 'AUTO'
                get_pose_bone(forearm_stretch_name + side).bone.bbone_handle_type_end = 'AUTO'

                # set constraints
                cns = get_pose_bone(arm_stretch_name + side).constraints.get("Copy Location")
                if cns:# backward-compatibility
                    cns.head_tail = 1.0

                # Enable twist deform and unhide
                c_arm_twist_offset = get_pose_bone(arm_twist_offset_name + side)
                forearm_twist = get_pose_bone(forearm_twist_name + side)

                if c_arm_twist_offset:
                    set_bone_layer(c_arm_twist_offset.bone, 0)
                    c_arm_twist_offset.bone.hide = False# backward-compatibility fix

                    if c_arm_twist_offset.bone.layers[22] == False:# if not disabled
                        c_arm_twist_offset.bone.use_deform = True

                # proxy
                c_arm_twist_offset_proxy = get_pose_bone(arm_twist_offset_name + "_proxy" + side)
                if c_arm_twist_offset_proxy:
                    set_bone_layer(c_arm_twist_offset_proxy.bone, 0)
                    c_arm_twist_offset_proxy.bone.hide = False# backward-compatibility fix

                if forearm_twist:
                    forearm_twist.bone.use_deform = True


                bpy.ops.object.mode_set(mode='EDIT')

                # Remove secondary controllers
                secondary_list = [c_shoulder_bend_name, c_arm_bend_name, c_elbow_bend_name, c_forearm_bend_name, c_wrist_bend_name]

                for bn in secondary_list:
                    ebn = get_edit_bone(bn+side)
                    if ebn:
                        delete_edit_bone(ebn)

                    # proxy
                for bn in secondary_list:
                    ebn_proxy = get_edit_bone(bn+'_proxy'+side)
                    if ebn_proxy:
                        delete_edit_bone(ebn_proxy)



                # Remove any additional twist controllers
                for bname in twist_bones_list:
                    b_twist = get_edit_bone(bname)
                    c_twist_name = 'c_' + bname
                    c_twist = get_edit_bone(c_twist_name)
                    # remove
                    if c_twist:
                        delete_edit_bone(c_twist)
                    # enable base twist bone deform, except the first (replaced by c_arm_twist_offset)
                    b_twist.use_deform = True
                    if ('twist.' in bname or 'twist_dupli' in bname) and not 'forearm' in bname:
                        #print("don't use deform for:", bname)
                        b_twist.use_deform = False
                        #print("SET DEFORM TO FALSE", b_twist.name)

                remove_twist_based_constraints(side)
                remove_twist_based_segments(side)
                remove_twist_based_bendy(side)
                remove_twist_based_str(side)

                bpy.ops.object.mode_set(mode='POSE')


    # Align fingers
    print("  Align fingers...")
    bpy.ops.object.mode_set(mode='EDIT')

    fingers_rot_prop = rig.rig_fingers_rot
    thumb_rot_prop = rig.rig_fingers_rot_thumb
    fingers_shape_type = rig.arp_fingers_shape_style
    
    fingers_align_dict = {
        ard.thumb_ref_dict['thumb1']: [ard.thumb_control_dict['base'], ard.thumb_control_dict['1'], ard.thumb_intern_dict['base'], ard.thumb_intern_dict['bend_all'], ard.thumb_intern_dict['rot1']],
        ard.thumb_ref_dict['thumb2']: [ard.thumb_control_dict['2'], ard.thumb_intern_dict['rot2']],
        ard.thumb_ref_dict['thumb3']: [ard.thumb_control_dict['3'], ard.thumb_intern_dict['rot3']],
        
        ard.index_ref_dict['index_meta']: [ard.index_control_dict['base']],            
        ard.index_ref_dict['index1']: [ard.index_control_dict['1'], ard.index_intern_dict['base'], ard.index_intern_dict['bend_all'], ard.index_intern_dict['rot1']],
        ard.index_ref_dict['index2']: [ard.index_control_dict['2'], ard.index_intern_dict['rot2']],
        ard.index_ref_dict['index3']: [ard.index_control_dict['3'], ard.index_intern_dict['rot3']],
        
        ard.middle_ref_dict['middle_meta']: [ard.middle_control_dict['base']],            
        ard.middle_ref_dict['middle1']: [ard.middle_control_dict['1'], ard.middle_intern_dict['base'], ard.middle_intern_dict['bend_all'], ard.middle_intern_dict['rot1']],
        ard.middle_ref_dict['middle2']: [ard.middle_control_dict['2'], ard.middle_intern_dict['rot2']],
        ard.middle_ref_dict['middle3']: [ard.middle_control_dict['3'], ard.middle_intern_dict['rot3']],
        
        ard.ring_ref_dict['ring_meta']: [ard.ring_control_dict['base']],            
        ard.ring_ref_dict['ring1']: [ard.ring_control_dict['1'], ard.ring_intern_dict['base'], ard.ring_intern_dict['bend_all'], ard.ring_intern_dict['rot1']],
        ard.ring_ref_dict['ring2']: [ard.ring_control_dict['2'], ard.ring_intern_dict['rot2']],
        ard.ring_ref_dict['ring3']: [ard.ring_control_dict['3'], ard.ring_intern_dict['rot3']],
        
        ard.pinky_ref_dict['pinky_meta']: [ard.pinky_control_dict['base']],            
        ard.pinky_ref_dict['pinky1']: [ard.pinky_control_dict['1'], ard.pinky_intern_dict['base'], ard.pinky_intern_dict['bend_all'], ard.pinky_intern_dict['rot1']],
        ard.pinky_ref_dict['pinky2']: [ard.pinky_control_dict['2'], ard.pinky_intern_dict['rot2']],
        ard.pinky_ref_dict['pinky3']: [ard.pinky_control_dict['3'], ard.pinky_intern_dict['rot3']],
        }
            
            
    for side in sides:

        fingers = []
        hand_def = get_edit_bone(hand_name + side)

        if hand_def == None:
            continue
        
        fingers_names = ard.fingers_control + ard.fingers_intern        
        
        for finger_ref_basename in fingers_align_dict:
            finger_ref_name = finger_ref_basename+side
            finger_ref = get_edit_bone(finger_ref_name)
            
            if finger_ref == None:
                continue            
          
            for finger_basename in fingers_align_dict[finger_ref_basename]:
                finger_name = finger_basename+side
                finger = get_edit_bone(finger_name)
             
                if finger == None:
                    continue
                
                copy_bone_transforms(finger_ref, finger)
                
                # option to separate the auto pinky bone from other base fingers bone               
                if finger_basename == ard.pinky_control_dict['base']:
                    pinky_auto = get_edit_bone("c_pinky1_auto" + side)
                    if pinky_auto:
                        pinky_auto.head = finger_ref.head + (finger_ref.z_axis.normalized() * (finger_ref.tail - finger_ref.head).magnitude * 0.15)
                        pinky_auto.tail = finger_ref.tail + (finger_ref.z_axis.normalized() * (finger_ref.tail - finger_ref.head).magnitude * 0.15)
                        pinky_auto.roll = finger_ref.roll
                
        
        # Set custom shape
        meta_fingers_names = [ard.thumb_control_dict['base'], ard.index_control_dict['base'], ard.middle_control_dict['base'], ard.ring_control_dict['base'], ard.pinky_control_dict['base']]
        
        bpy.ops.object.mode_set(mode='POSE')

        for finger_base_name in ard.fingers_control:
            finger_name = finger_base_name + side
            finger_pb = get_pose_bone(finger_name)

            if finger_pb == None:
                continue

            if finger_pb.custom_shape:
                if not "cs_user" in finger_pb.custom_shape.name:
                    if not finger_base_name in meta_fingers_names:#exclude meta/base fingers shapes                    
                        cs_obj = None
                        if fingers_shape_type == "box":
                            cs_obj = get_object("cs_box")

                        if fingers_shape_type == "circle":
                            cs_obj = get_object("cs_torus_04")

                        if cs_obj:
                            finger_pb.custom_shape = cs_obj


        bpy.ops.object.mode_set(mode='EDIT')
        
        fingers_control_1 = {
            ard.thumb_control_dict['1']: [ard.thumb_intern_dict['base'], ard.thumb_intern_dict['rot2'], ard.thumb_intern_dict['bend_all']],
            ard.index_control_dict['1']: [ard.index_intern_dict['base'], ard.index_intern_dict['rot2'], ard.index_intern_dict['bend_all']], 
            ard.middle_control_dict['1']: [ard.middle_intern_dict['base'], ard.middle_intern_dict['rot2'], ard.middle_intern_dict['bend_all']], 
            ard.ring_control_dict['1']: [ard.ring_intern_dict['base'], ard.ring_intern_dict['rot2'], ard.ring_intern_dict['bend_all']], 
            ard.pinky_control_dict['1']: [ard.pinky_intern_dict['base'], ard.pinky_intern_dict['rot2'], ard.pinky_intern_dict['bend_all']]
            }
        
        print("  Setup fingers rotations...")
        
        
        for base_finger_name in fingers_control_1:
            finger_name = base_finger_name+side
            finger_ebone  = get_edit_bone(finger_name)
        
            if finger_ebone == None:
                continue

            # set rot from scale
            add_bone_name = fingers_control_1[base_finger_name][0]+side#finger_name[2:]# thumb1
            rot_bone_name = fingers_control_1[base_finger_name][1]+side
            bend_all_name = fingers_control_1[base_finger_name][2]+side        
            
            valid_prop = fingers_rot_prop
            
            if 'thumb' in finger_name:
                valid_prop = thumb_rot_prop                
            
            # if scale-rotation is set
            if valid_prop != 'no_scale':                
                # create bone if necessary
                if get_edit_bone(add_bone_name) == None:
                    new_bone = rig.data.edit_bones.new(add_bone_name)
                    copy_bone_transforms(finger_ebone, new_bone)

                    # set layer                        
                    set_bone_layer(new_bone, 8)

                    # set deform
                    finger_ebone.use_deform = False
                    new_bone.use_deform = True

                    # set parent
                    new_bone.parent = finger_ebone
                    get_edit_bone(rot_bone_name).parent = new_bone

                    # set constraint
                    bpy.ops.object.mode_set(mode='POSE')

                    cns = get_pose_bone(add_bone_name).constraints.new('COPY_SCALE')
                    cns.target = rig
                    cns.subtarget = hand_name + side

                    # set custom shape transform
                    get_pose_bone(finger_name).custom_shape_transform = get_pose_bone(add_bone_name)

                    bpy.ops.object.mode_set(mode='EDIT')

                # assign parameters
                get_edit_bone(add_bone_name).use_inherit_scale = False
                
                bpy.ops.object.mode_set(mode='POSE')

                enable_constraint(get_pose_bone(add_bone_name).constraints[0], True)

                # create new driver var if necessary              
                dp = 'pose.bones["' + bend_all_name + '"].rotation_euler'
                dr = rig.animation_data.drivers.find(dp)

                if dr:
                    found_var = False
                    for var in dr.driver.variables:
                        if "var_002" in var.name:
                            found_var = True

                    if not found_var:
                        new_var = dr.driver.variables.new()
                        new_var.name = "var_002"
                        new_var.type = 'SINGLE_PROP'
                        new_var.targets[0].id = dr.driver.variables[0].targets[0].id
                        new_var.targets[0].data_path = 'pose.bones["' + finger_name + '"].scale[0]'

                    dr.driver.expression = '-var - var_001 - (1-var_002)*2.5'
            
                    add_pbone = get_pose_bone(add_bone_name)
                    
                    if add_pbone:
                        const = [x for x in add_pbone.constraints if x.type == "COPY_ROTATION"]
                        if len(const) > 0:
                            if valid_prop == 'scale_2_phalanges':
                                const[0].influence = 0.0
                            else:# scale_3_phalanges
                                const[0].influence = 1.0
                        else:
                            print(' '+add_bone_name + ": No constraint found, could not configure auto fingers rotation")
                    else:
                        print(' '+add_bone_name, "not found")
                else:
                    print(" driver:", 'pose.bones["' + bend_all_name + '"].rotation_euler',
                          'not found, could not configure auto fingers rotation')
            
            
            else:# "no_scale"
                # only if the new bone setup exists
                if get_edit_bone(add_bone_name):
                    # assign params
                    get_edit_bone(add_bone_name).use_inherit_scale = True
                    
                    bpy.ops.object.mode_set(mode='POSE')
                    
                    try:                      
                        enable_constraint(get_pose_bone(add_bone_name).constraints[0], False)
                    except:
                        pass

                    # set driver expressions
                    dp = 'pose.bones["' + bend_all_name + '"].rotation_euler'
                    dr = bpy.context.active_object.animation_data.drivers.find(dp)
                    dr.driver.expression = '-var - var_001'

                    
            bpy.ops.object.mode_set(mode='EDIT')
            
            
        # set auto rotation constraint from the pinky finger if any
        bpy.ops.object.mode_set(mode='POSE')
        
        fingers_autorot_dict = {'c_middle1_base': 0.33, 'c_ring1_base': 0.66}
        
        for finger_name in fingers_autorot_dict:
            pinky = get_pose_bone("c_pinky1_base" + side)
            # set the constraint if there's the pinky
            current_finger = get_pose_bone(finger_name+side)
            if current_finger and pinky:
                cns = current_finger.constraints.get("Copy Rotation")
                if cns == None:
                    cns = current_finger.constraints.new("COPY_ROTATION")
                    cns.name = "Copy Rotation"
                cns.target = bpy.context.active_object
                # if there's pinky_auto, use it as target instead of the base pinky
                pinky_auto = get_pose_bone("c_pinky1_auto"+side)
                cns.subtarget = pinky.name if pinky_auto == None else pinky_auto.name
                cns.use_offset = True
                cns.owner_space = cns.target_space = 'LOCAL'
                cns.influence = fingers_autorot_dict[finger_name]

            # remove the constraint if there's no pinky
            if current_finger and not pinky:
                cns = current_finger.constraints.get("Copy Rotation")
                if cns:
                    current_finger.constraints.remove(cns)

                    
        bpy.ops.object.mode_set(mode='EDIT')

        
        # Fingers IK-FK
        hand_ref = get_edit_bone(hand_ref_name+side)
        if "fingers_ik" in hand_ref.keys():# backward-compatibility
            if hand_ref["fingers_ik"]:

                pole_angles_dict = {}
                align_fingers_ik(side, pole_angles_dict)

                bpy.ops.object.mode_set(mode='POSE')

                set_fingers_ik_angle(pole_angles_dict, side)

                bpy.ops.object.mode_set(mode='EDIT')

    if scn.arp_debug_mode == True:
        print("\n FINISHED ALIGNING ARM BONES...\n")


def align_fingers_ik(side, pole_angles_dict):
    print("  Align Fingers IK...")

    hand_ref = get_edit_bone("hand_ref"+side)

    fingers_ik_pole_distance = 1.0
    if "fingers_ik_pole_distance" in hand_ref.keys():
        fingers_ik_pole_distance = hand_ref["fingers_ik_pole_distance"]

    for fing_type in ["thumb", "index", "middle", "ring", "pinky"]:
        # only if finger is there
        phal1_ref = get_edit_bone(fing_type+"1_ref"+side)
        if phal1_ref == None:
            continue

        # IK chain
        for fi in range(1, 4):
            f_idx = str(fi)
            ref_name = fing_type+f_idx+"_ref"+side
            ref_bone = get_edit_bone(ref_name)
            c_ik_name = "c_"+fing_type+f_idx+"_ik"+side
            c_ik = get_edit_bone(c_ik_name)

            copy_bone_transforms(ref_bone, c_ik)

        # IK target 1 (tip)
        ik_target_name = "c_"+fing_type+"_ik"+side
        c_ik_target = get_edit_bone(ik_target_name)
        phal3_ref_name = fing_type+"3_ref"+side
        phal3_ref = get_edit_bone(phal3_ref_name)

        copy_bone_transforms(phal3_ref, c_ik_target)
        roll_copy = c_ik_target.roll
        y_offset = c_ik_target.tail-c_ik_target.head
        c_ik_target.head, c_ik_target.tail = c_ik_target.head + y_offset, c_ik_target.tail + y_offset
        c_ik_target.roll = roll_copy

        #IK target 2 (root)
        ik_target2_name = "c_"+fing_type+"_ik2"+side
        c_ik_target2 = get_edit_bone(ik_target2_name)
        phal3_ref_name = fing_type+"3_ref"+side
        phal3_ref = get_edit_bone(phal3_ref_name)

        copy_bone_transforms(phal3_ref, c_ik_target2)


        # IK pole
        ik_pole_name = "c_"+fing_type+"_pole"+side
        c_ik_pole = get_edit_bone(ik_pole_name)
        phal1_ref_name = fing_type+"1_ref"+side
        phal1_ref = get_edit_bone(phal1_ref_name)
        phal2_ref_name = fing_type+"2_ref"+side
        phal2_ref = get_edit_bone(phal2_ref_name)

        copy_bone_transforms(phal2_ref, c_ik_pole)
        roll_copy = c_ik_pole.roll
        z_offset = phal2_ref.z_axis.normalized() * (phal2_ref.tail-phal2_ref.head).magnitude * 1.3 * fingers_ik_pole_distance
        c_ik_pole.head, c_ik_pole.tail = c_ik_pole.head + z_offset, c_ik_pole.tail + z_offset
        c_ik_pole.tail = c_ik_pole.head + (c_ik_pole.tail-c_ik_pole.head)*0.5
        c_ik_pole.roll = roll_copy

        # get IK pole angle
        pole_angles_dict[fing_type] = get_pole_angle(phal1_ref, phal3_ref, c_ik_pole.head)


def mirror_roll(bone, side):
    if side[-2:] == ".r":
        get_edit_bone(bone).roll *= -1


def parent_retarget(ref_bone):
    scn = bpy.context.scene

    retargetted_parent = None
    is_a_ref_bone = False

    if ref_bone.parent.name[:-2][-4:] == "_ref":
        is_a_ref_bone = True

    if "_ref_dupli_" in ref_bone.parent.name:
        is_a_ref_bone = True

    if is_a_ref_bone:
        
        print(ref_bone.name, "is parented to a ref bone")
        
        if "_ref_dupli_" in ref_bone.parent.name:
            control_parent_name = 'c_' + ref_bone.parent.name.replace('_ref_dupli_', '_dupli_')
        else:
            control_parent_name = 'c_' + ref_bone.parent.name.replace('_ref.', '.')

        retargetted_parent = get_edit_bone(control_parent_name)
     
        #print("ref_bone", ref_bone)
        #print("retargetted_parent", retargetted_parent)
        
        if retargetted_parent:
            parent_name = retargetted_parent.name
            
            # in case of spline ik limb as parent, use the tip bone if the last bone is set
            if 'arp_spline' in retargetted_parent.keys():#if parent_name.startswith("c_spline_"):
                print('spline IK parent...')
                #id = parent_name.split('_')[2].split('.')[0]
                spline_side = get_bone_side(parent_name)
                spline_name = get_spline_name(spline_side)
                name_split = parent_name.split('_')
                id = name_split[len(name_split)-1].split('.')[0]
                id_1_str = '%02d' % (1)
                print("found a c_spline parent, id :", id)
                first_spline_bone_name = spline_name+'_'+id_1_str+'_ref'+spline_side# "spline_01_ref" + parent_name[11:]
                print('first_spline_bone_name:', first_spline_bone_name)
                first_spline_bone = get_edit_bone(first_spline_bone_name)
                spline_count = first_spline_bone["spline_count"]

                if id == '%02d' % (spline_count):
                    print("The c_spline parent is the last bone, use the tip instead")
                    n = parent_name.replace(id, '%02d' % (spline_count + 1))
                    if 'spline_type' in first_spline_bone.keys():#backward-compatibility
                        if first_spline_bone['spline_type'] == '2':
                            n = parent_name.replace(id, 'inter_' + '%02d' % (spline_count + 1))
                    print(n)
                    retargetted_parent = get_edit_bone(n)
    else:
        retargetted_parent = ref_bone.parent

    return retargetted_parent


# driver creation function
def configure_driver_bbone(driv=None, bone=None, b_side=None, loc=None, type=None, fac=None):
    _expression = "var"
    if fac:
        _expression += "*" + str(fac)

    driv.driver.expression = _expression

    # create a new var if necessary
    if len(driv.driver.variables) == 0:
        base_var = driv.driver.variables.new()
    else:
        base_var = driv.driver.variables[0]

    base_var.type = 'SINGLE_PROP'
    base_var.name = 'var'
    base_var.targets[0].id = bpy.context.active_object

    if type == "location":
        base_var.targets[0].data_path = 'pose.bones["' + bone + b_side + '"].location[' + str(loc) + ']'
    if type.startswith("scale"):
        i = 0
        if type == 'scale_y':
            i = 1
        elif type == 'scale_z':
            i = 2
        base_var.targets[0].data_path = 'pose.bones["' + bone + b_side + '"].scale['+str(i)+']'
    if type == "rotation":
        base_var.targets[0].data_path = 'pose.bones["' + bone + b_side + '"].rotation_euler[1]'


def set_secondary_drivers(drivers_list, bone_name_list, side, bone_length, enable_ease_out_dr=True):
    rig = bpy.context.active_object
    
    driver_in_x = None
    driver_out_x = None
    driver_in_y = None
    driver_out_y = None
    driver_scale_in_x = None
    driver_scale_in_y = None
    driver_scale_in_z = None
    driver_scale_out_x = None
    driver_scale_out_y = None
    driver_scale_out_z = None
    driver_rot_in = None
    driver_rot_out = None
    driver_ease_in = None
    driver_ease_out = None
    

    # are the drivers already created?
    for dri in drivers_list:
        if '"' + bone_name_list[0] + side in dri.data_path:
            # curve in x, y
            if "bbone_curveinx" in dri.data_path:
                driver_in_x = [dri.data_path, dri.array_index]
            if get_bbone_param_name('bbone_curveinz') in dri.data_path:
                driver_in_y = [dri.data_path, dri.array_index]
            # curve out x, y
            if "bbone_curveoutx" in dri.data_path:
                driver_out_x = [dri.data_path, dri.array_index]
            if get_bbone_param_name('bbone_curveoutz') in dri.data_path:
                driver_out_y = [dri.data_path, dri.array_index]
            # scale in x,y,z
            if is_fc_bb_param(dri, 'bbone_scaleinx'):
                driver_scale_in_x = [dri.data_path, dri.array_index]
            if is_fc_bb_param(dri, 'bbone_scaleiny'):
                driver_scale_in_y = [dri.data_path, dri.array_index]
            if is_fc_bb_param(dri, 'bbone_scaleinz'):
                driver_scale_in_z = [dri.data_path, dri.array_index]
            # scale out x,y,z
            if is_fc_bb_param(dri, 'bbone_scaleoutx'):
                driver_scale_out_x = [dri.data_path, dri.array_index]
            if is_fc_bb_param(dri, 'bbone_scaleouty'):
                driver_scale_out_y = [dri.data_path, dri.array_index]
            if is_fc_bb_param(dri, 'bbone_scaleoutz'):
                driver_scale_out_z = [dri.data_path, dri.array_index]
            # roll in, out
            if "bbone_rollin" in dri.data_path:
                driver_rot_in = [dri.data_path, dri.array_index]
            if "bbone_rollout" in dri.data_path:
                driver_rot_out = [dri.data_path, dri.array_index]
            # ease in, out
            if "bbone_easein" in dri.data_path:
                driver_ease_in = [dri.data_path, dri.array_index]
            if "bbone_easeout" in dri.data_path:
                driver_ease_out = [dri.data_path, dri.array_index]

    fac_offset = "2.2"
    fac_ease = "8/"

    # Driver In X
    if driver_in_x:
        dr_inx = drivers_list.find(driver_in_x[0])
    else:
        dr_inx = rig.driver_add('pose.bones["' + bone_name_list[0] + side + '"].bbone_curveinx', -1)

    configure_driver_bbone(driv=dr_inx, bone=bone_name_list[1], b_side=side, loc=0, type="location", fac=fac_offset)

    # Driver In Y
    if driver_in_y:
        dr_iny = drivers_list.find(driver_in_y[0])
    else:
        dr_iny = rig.driver_add('pose.bones["' + bone_name_list[0] + side + '"].'+get_bbone_param_name('bbone_curveinz'), -1)

    configure_driver_bbone(driv=dr_iny, bone=bone_name_list[1], b_side=side, loc=2, type="location", fac=fac_offset)

    # Driver Out X
    if driver_out_x:
        dr_outx = drivers_list.find(driver_out_x[0])
    else:
        dr_outx = rig.driver_add('pose.bones["' + bone_name_list[0] + side + '"].bbone_curveoutx', -1)

    configure_driver_bbone(driv=dr_outx, bone=bone_name_list[2], b_side=side, loc=0, type="location", fac=fac_offset)

    # Driver Out Y
    if driver_out_y:
        dr_outy = drivers_list.find(driver_out_y[0])
    else:
        dr_outy = rig.driver_add('pose.bones["' + bone_name_list[0] + side + '"].'+get_bbone_param_name('bbone_curveoutz'), -1)

    configure_driver_bbone(driv=dr_outy, bone=bone_name_list[2], b_side=side, loc=2, type="location", fac=fac_offset)

    # Driver Scale In X
    if driver_scale_in_x:
        dr_scaleinx = drivers_list.find(driver_scale_in_x[0], index=driver_scale_in_x[1])
    else:
        arr_idx = 0 if bpy.app.version >= (3,0,0) else -1
        dr_scaleinx = rig.driver_add('pose.bones["' + bone_name_list[0] + side + '"].'+get_bbone_param_name('bbone_scaleinx'), arr_idx)

    configure_driver_bbone(driv=dr_scaleinx, bone=bone_name_list[1], b_side=side, type="scale_x")

    # Driver Scale In Y
    if driver_scale_in_y:
        dr_scaleiny = drivers_list.find(driver_scale_in_y[0], index=driver_scale_in_y[1])
    else:
        arr_idx = 1 if bpy.app.version >= (3,0,0) else -1
        dr_scaleiny = rig.driver_add('pose.bones["' + bone_name_list[0] + side + '"].'+get_bbone_param_name('bbone_scaleiny'), arr_idx)

    configure_driver_bbone(driv=dr_scaleiny, bone=bone_name_list[1], b_side=side, type="scale_y")
    
    if bpy.app.version >= (3,0,0):
        # Driver Scale In Z for Blender 3.0 and higher
        if driver_scale_in_z:
            dr_scaleinz = drivers_list.find(driver_scale_in_z[0], index=driver_scale_in_z[1])
        else:
            dr_scaleinz = rig.driver_add('pose.bones["' + bone_name_list[0] + side + '"].bbone_scalein', 2)

        configure_driver_bbone(driv=dr_scaleinz, bone=bone_name_list[1], b_side=side, type="scale_z")

    # Driver Scale Out X
    if driver_scale_out_x:
        dr_scaleoutx = drivers_list.find(driver_scale_out_x[0], index=driver_scale_out_x[1])
    else:
        arr_idx = 0 if bpy.app.version >= (3,0,0) else -1
        dr_scaleoutx = rig.driver_add('pose.bones["' + bone_name_list[0] + side + '"].'+get_bbone_param_name('bbone_scaleoutx'), arr_idx)

    configure_driver_bbone(driv=dr_scaleoutx, bone=bone_name_list[2], b_side=side, type="scale_x")

    # Driver Scale Out Y
    if driver_scale_out_y:
        dr_scaleouty = drivers_list.find(driver_scale_out_y[0], index=driver_scale_out_y[1])
    else:
        arr_idx = 1 if bpy.app.version >= (3,0,0) else -1
        dr_scaleouty = rig.driver_add('pose.bones["' + bone_name_list[0] + side + '"].'+get_bbone_param_name('bbone_scaleouty'), arr_idx)

    configure_driver_bbone(driv=dr_scaleouty, bone=bone_name_list[2], b_side=side, type="scale_y")
    
    if bpy.app.version >= (3,0,0):
        # Driver Scale Out Z for Blender 3.0 and higher
        if driver_scale_out_z:
            dr_scaleoutz = drivers_list.find(driver_scale_out_z[0], index=driver_scale_out_z[1])
        else:
            dr_scaleoutz = rig.driver_add('pose.bones["' + bone_name_list[0] + side + '"].bbone_scaleout', 2)

        configure_driver_bbone(driv=dr_scaleoutz, bone=bone_name_list[2], b_side=side, type="scale_z")

    # Driver Rot In
    if driver_rot_in:
        dr_rotin = drivers_list.find(driver_rot_in[0])
    else:
        dr_rotin = rig.driver_add('pose.bones["' + bone_name_list[0] + side + '"].bbone_rollin', -1)

    configure_driver_bbone(driv=dr_rotin, bone=bone_name_list[1], b_side=side, type="rotation")

    # Driver Rot Out
    if driver_rot_out:
        dr_rotout = drivers_list.find(driver_rot_out[0])
    else:
        dr_rotout = rig.driver_add('pose.bones["' + bone_name_list[0] + side + '"].bbone_rollout', -1)

    configure_driver_bbone(driv=dr_rotout, bone=bone_name_list[2], b_side=side, type="rotation")

    # Driver Ease In
    if driver_ease_in:
        dr_easin = drivers_list.find(driver_ease_in[0])
    else:
        dr_easin = rig.driver_add('pose.bones["' + bone_name_list[0] + side + '"].bbone_easein', -1)

    configure_driver_bbone(driv=dr_easin, bone=bone_name_list[1], b_side=side, loc=1, type="location",
                           fac=fac_ease + str(bone_length))

    # Driver Ease Out
    if enable_ease_out_dr:
        if driver_ease_out:
            dr_easout = drivers_list.find(driver_ease_out[0])
        else:
            dr_easout = rig.driver_add( 'pose.bones["' + bone_name_list[0] + side + '"].bbone_easeout', -1)

        configure_driver_bbone(driv=dr_easout, bone=bone_name_list[2], b_side=side, loc=1, type="location",
                               fac='-' + fac_ease + str(bone_length))

    else:
        try:
            rig.driver_remove('pose.bones["' + bone_name_list[0] + side + '"].bbone_easeout', -1)
        except:
            pass


def _align_leg_limbs():
    print("\n Align leg bones...\n")
    
    scn = bpy.context.scene
    sides = limb_sides.leg_sides

    thigh_b_ref_name = ard.leg_ref_bones_dict['thigh_b']
    thigh_ref_name = ard.leg_ref_bones_dict['thigh']
    leg_ref_name = ard.leg_ref_bones_dict['calf']
    foot_ref_name = ard.leg_ref_bones_dict['foot']
    toes_ref_name = ard.leg_ref_bones_dict['toes']
    
    prepole_name = ard.leg_bones_dict['prepole']#"leg_fk_pre_pole"
    fk_pole_name = ard.leg_bones_dict['fk_pole']#"leg_fk_pole"
    ik_pole_name = ard.leg_bones_dict['control_pole_ik']#"c_leg_pole"
    foot_pole_name = ard.leg_bones_dict['foot']['pole']#"foot_pole"
    stretch_leg_name = ard.leg_bones_dict['control_stretch']#"c_stretch_leg"
    pin_leg_name = ard.leg_bones_dict['control_pin']
    
    c_thigh_ik_name = ard.leg_bones_dict['thigh']['control_ik']    
    c_thigh_fk_name = ard.leg_bones_dict['thigh']['control_fk']   
    thigh_fk_name = ard.leg_bones_dict['thigh']['fk']        
    thigh_ik_nostr_name = ard.leg_bones_dict['thigh']['ik_nostr']
    thigh_ik_name = ard.leg_bones_dict['thigh']['ik']
    thigh_twist_name = ard.leg_bones_dict['thigh']['twist']
    thigh_stretch_name = ard.leg_bones_dict['thigh']['stretch']
    thigh_name = ard.leg_bones_dict['thigh']['base']
    
    c_leg_fk_name = ard.leg_bones_dict['calf']['control_fk']
    leg_fk_name = ard.leg_bones_dict['calf']['fk']
    leg_ik_nostr_name = ard.leg_bones_dict['calf']['ik_nostr']
    leg_ik_name = ard.leg_bones_dict['calf']['ik']    
    leg_twist_name = ard.leg_bones_dict['calf']['twist']    
    leg_stretch_name = ard.leg_bones_dict['calf']['stretch']    
    leg_name = ard.leg_bones_dict['calf']['base']


    c_thigh_bend_contact_name = ard.leg_bones_dict['thigh']['secondary_00']   
    c_thigh_bend_01_name = ard.leg_bones_dict['thigh']['secondary_01']
    c_thigh_bend_02_name = ard.leg_bones_dict['thigh']['secondary_02']    
    c_knee_bend_name = ard.leg_bones_dict['calf']['secondary_00']
    c_leg_bend_01_name = ard.leg_bones_dict['calf']['secondary_01']
    c_leg_bend_02_name = ard.leg_bones_dict['calf']['secondary_02']
    c_ankle_bend_name = ard.leg_bones_dict['calf']['secondary_03']
    
    foot_name = ard.leg_bones_dict['foot']['deform']
    foot_fk_name = ard.leg_bones_dict['foot']['fk']
    c_foot_fk_name = ard.leg_bones_dict['foot']['control_fk']
    foot_ik_name = ard.leg_bones_dict['foot']['ik']
    c_foot_ik_name = ard.leg_bones_dict['foot']['control_ik']
    c_foot_ik_offset_name = "c_foot_ik_offset"
    foot_snap_fk_name = ard.leg_bones_dict['foot']['snap_fk']
    foot_ik_target_name = ard.leg_bones_dict['foot']['ik_target']
    
    foot_bank_01_name = ard.leg_bones_dict['foot']['bank_01']
    foot_bank_02_name = ard.leg_bones_dict['foot']['bank_02']
    c_foot_heel_name = ard.leg_bones_dict['foot']['foot_heel']
    c_foot_01_name = ard.leg_bones_dict['foot']['control_reverse']
    foot_fk_scale_fix_name = ard.leg_bones_dict['foot']['fk_scale_fix']
    
    
    thighs = [c_thigh_ik_name, c_thigh_fk_name, thigh_fk_name, thigh_ik_nostr_name, thigh_ik_name, thigh_twist_name, thigh_stretch_name, thigh_name]
    
    legs = [c_leg_fk_name, leg_fk_name, leg_ik_nostr_name, leg_ik_name, leg_twist_name, leg_stretch_name, leg_name]
    
    leg_bends = [c_thigh_bend_contact_name, c_thigh_bend_01_name, c_thigh_bend_02_name, c_knee_bend_name, c_leg_bend_01_name, c_leg_bend_02_name, c_ankle_bend_name]
    
    
    feet = [foot_name, foot_fk_name, c_foot_fk_name, foot_ik_name, c_foot_ik_name, c_foot_ik_offset_name, foot_snap_fk_name,
            foot_ik_target_name, foot_bank_01_name, foot_bank_02_name, c_foot_heel_name, c_foot_01_name, foot_fk_scale_fix_name]

    rig = get_object(bpy.context.active_object.name)

    
    bpy.ops.object.mode_set(mode='EDIT')
    
    # get ik-fk default value
    legs_ikfk_default_dict = {}
    for side in sides:
        foot_ref = get_edit_bone(foot_ref_name+side)
        
        if 'ikfk_default' in foot_ref.keys():
            val = foot_ref['ikfk_default']
            if val == "IK" or val == "FK":
                legs_ikfk_default_dict[side] = val
                continue        
                
        legs_ikfk_default_dict[side] = bpy.context.preferences.addons[__package__].preferences.default_ikfk_leg

    # align thighs
    for side in sides:
        print("  [", side, "]")
        
        thigh_ref = get_edit_bone(thigh_ref_name + side)       
        leg_ref = get_edit_bone(leg_ref_name + side)
        
        # ik, fk, base
        for bname in [c_thigh_ik_name, c_thigh_fk_name, thigh_fk_name, thigh_ik_nostr_name, thigh_ik_name, thigh_name]:
            current_bone = get_edit_bone(bname+side)
            if current_bone == None:
                print("Warning,", bname+side, "not found, skip")
                continue
            copy_bone_transforms(thigh_ref, current_bone)                  
                
        # twist
        thigh_twist = get_edit_bone(thigh_twist_name+side)
        if thigh_twist:
            thigh_twist.head = thigh_ref.head.copy()
            thigh_twist.tail = thigh_ref.head + (thigh_ref.tail - thigh_ref.head) * 0.5
        else:
            print("Warning,", thigh_twist_name+side, "not found, skip")
            
        
        # stretch
        thigh_stretch = get_edit_bone(thigh_stretch_name+side)
        
        if thigh_stretch:
            if rig.arp_secondary_type != "BENDY_BONES":
                thigh_stretch.bbone_segments = 0
                thigh_stretch.head = thigh_ref.head + (thigh_ref.tail - thigh_ref.head) * 0.5
                thigh_stretch.tail = thigh_ref.tail.copy()
                
            if rig.arp_secondary_type == "BENDY_BONES":
                thigh_stretch.bbone_segments = 20
                copy_bone_transforms(thigh_ref, thigh_stretch)        
        else:
            print("Warning,", thigh_stretch_name+side, "not found, skip")       
        

        # align legs
        for bname in legs:
            current_bone = get_edit_bone(bname + side)            

            if current_bone and leg_ref:
                if not 'stretch' in bname and not 'twist' in bname:
                    current_bone.head = leg_ref.head.copy()
                    current_bone.tail = leg_ref.tail.copy()
                else:
                    if 'twist' in bname:
                        current_bone.head = leg_ref.head + (leg_ref.tail - leg_ref.head) * 0.5
                        current_bone.tail = leg_ref.tail.copy()
                    if 'stretch' in bname:
                        if rig.arp_secondary_type != "BENDY_BONES":
                            current_bone.bbone_segments = 0
                            current_bone.head = leg_ref.head.copy()
                            current_bone.tail = leg_ref.head + (leg_ref.tail - leg_ref.head) * 0.5

                        if rig.arp_secondary_type == "BENDY_BONES":
                            current_bone.bbone_segments = 20
                            current_bone.head = leg_ref.head.copy()
                            current_bone.tail = leg_ref.tail.copy()

        if "bname" in locals():
            del bname

    print('  Clean drivers...')
    
    # Delete drivers of bendy bones if any. Must be done now, generates cyclic dependencies and possible crash otherwise
    dp_string_todel = []
    if rig.arp_secondary_type != "BENDY_BONES":
        dp_string_todel += ['"leg_stretch', '"thigh_stretch']
    if rig.arp_secondary_type != "TWIST_BASED":
        dp_string_todel += ['"thigh_bendy', '"leg_bendy']

    if len(dp_string_todel):
        drivers_list = rig.animation_data.drivers
        deleted_drivers_count = 0

        for side in sides:
            for dri in drivers_list:
                found = False
                for dp in dp_string_todel:
                    if dp + side in dri.data_path:
                        found = True
                if found:
                    prop_list = ['bbone_curveinx', get_bbone_param_name('bbone_curveinz'), 'bbone_curveoutx', get_bbone_param_name('bbone_curveoutz'),
                                 'bbone_scalein', 'bbone_scaleout',
                                 'bbone_rollin', 'bbone_rollout', 'bbone_easein', 'bbone_easeout']
                    found_prop = False
                    for p_i in prop_list:
                        if p_i in dri.data_path:
                            found_prop = True
                            break

                    if not found_prop:
                        continue
                    try:
                        rig.driver_remove(dri.data_path, -1)
                    except:
                        # something prevents to remove the driver. A workaround is to change the data_path before removing.
                        dri.data_path = "delta_scale"
                        rig.driver_remove(dri.data_path, -1)

                    deleted_drivers_count += 1

        print("  Deleted", deleted_drivers_count, "drivers")


    def align_leg_bend_bones(side):
        thigh_ref = get_edit_bone(thigh_ref_name + side)
        leg_ref = get_edit_bone(leg_ref_name + side)
        thigh_vec = thigh_ref.tail - thigh_ref.head
        leg_vec = leg_ref.tail - leg_ref.head
        length = 0.04

        for bname in leg_bends:
            current_bone = get_edit_bone(bname + side)

            if current_bone:
                if "contact" in bname:
                    current_bone.head = thigh_ref.head + thigh_vec * 0.15
                    current_bone.tail = current_bone.head + (thigh_ref.y_axis * length * leg_ref.length * 3)
                    current_bone.roll = thigh_ref.roll

                if "thigh_bend_01" in bname:
                    current_bone.head = thigh_ref.head + thigh_vec * 0.4
                    current_bone.tail = current_bone.head + (thigh_ref.y_axis * length * leg_ref.length * 3)
                    current_bone.roll = thigh_ref.roll

                if "thigh_bend_02" in bname:
                    current_bone.head = thigh_ref.head + thigh_vec * 0.75
                    current_bone.tail = current_bone.head + (thigh_ref.y_axis * length * leg_ref.length * 3)
                    current_bone.roll = thigh_ref.roll

                if "knee" in bname:
                    current_bone.head = thigh_ref.tail
                    current_bone.tail = current_bone.head + (thigh_ref.y_axis * length * leg_ref.length * 3)
                    current_bone.roll = thigh_ref.roll

                if "leg_bend_01" in bname:
                    current_bone.head = leg_ref.head + leg_vec * 0.25
                    current_bone.tail = current_bone.head + (leg_ref.y_axis * length * leg_ref.length * 3)
                    current_bone.roll = leg_ref.roll

                if "leg_bend_02" in bname:
                    current_bone.head = leg_ref.head + leg_vec * 0.6
                    current_bone.tail = current_bone.head + (leg_ref.y_axis * length * leg_ref.length * 3)
                    current_bone.roll = leg_ref.roll

                if "ankle" in bname:
                    current_bone.head = leg_ref.head + leg_vec * 0.85
                    current_bone.tail = current_bone.head + (leg_ref.y_axis * length * leg_ref.length * 3)
                    current_bone.roll = leg_ref.roll

    
    def get_auto_ik_roll():
        auto_ik_roll = True
        auto_ik_roll_name = ard.leg_props['auto_ik_roll']
        thigh_ref = get_edit_bone(thigh_ref_name+side)
        if auto_ik_roll_name in thigh_ref.keys():#backward-compatibility
            auto_ik_roll = thigh_ref[auto_ik_roll_name]        
        return auto_ik_roll
            
    # IK pole position
    for side in sides:
        thigh_ref = get_edit_bone(thigh_ref_name + side)
        leg_ref = get_edit_bone(leg_ref_name + side)
        foot_ref = get_edit_bone(foot_ref_name + side)
        prepole = get_edit_bone(prepole_name + side)

        if prepole and thigh_ref and leg_ref:
            # center the prepole in the middle of the chain
            prepole.head[0] = (thigh_ref.head[0] + leg_ref.tail[0]) / 2
            prepole.head[1] = (thigh_ref.head[1] + leg_ref.tail[1]) / 2
            prepole.head[2] = (thigh_ref.head[2] + leg_ref.tail[2]) / 2
            
            # point toward the knee
            prepole.tail = thigh_ref.tail.copy() 

            # Align FK pole
            fk_pole = get_edit_bone(fk_pole_name + side)
            # get legs plane normal
            plane_normal = (thigh_ref.head - leg_ref.tail)
            # pole position
            prepole_dir = prepole.tail - prepole.head
            pole_pos = prepole.tail + (prepole_dir).normalized()

            # ortho project onto plane to align with the knee/elbow
            pole_pos = project_point_onto_plane(pole_pos, prepole.tail, plane_normal)

            # make sure to keep a correct distance from the knee
            ik_pole_distance = 1.0
            if foot_ref.get("ik_pole_distance"):
                ik_pole_distance = foot_ref.get("ik_pole_distance")

            pole_pos = thigh_ref.tail + ((pole_pos - thigh_ref.tail).normalized() * (thigh_ref.tail - thigh_ref.head).magnitude * ik_pole_distance)
            
            auto_ik_roll = get_auto_ik_roll()
            if not auto_ik_roll:                
                fac = 1 if side.endswith('.l') else -1
                point_on_plane = ((thigh_ref.head+leg_ref.tail)/2) + (thigh_ref.x_axis.normalized() * fac * thigh_ref.length)
                pole_pos = project_point_onto_plane(pole_pos, point_on_plane, thigh_ref.z_axis)
                
            # set
            fk_pole.head = pole_pos
            fk_pole.tail = Vector((pole_pos)) + prepole_dir

            # Align IK pole
            ik_pole = get_edit_bone(ik_pole_name + side)
            ik_pole.head = fk_pole.head
            ik_pole.tail = [ik_pole.head[0], ik_pole.head[1], ik_pole.head[2] + (0.1 * thigh_ref.length * 2)]

            # reset the IK pole pose rotation
            bpy.ops.object.mode_set(mode='POSE')
            
            ik_pose_pole = get_pose_bone(ik_pole_name + side)
            ik_pose_pole.rotation_euler = [0, 0, 0]
            
            bpy.ops.object.mode_set(mode='EDIT')
            

    print('  Set roll...')
    # set thigh and leg roll
    for side in sides:
        # ik align?        
        auto_ik_roll = get_auto_ik_roll()
        
        if not auto_ik_roll:  
            continue
            
        leg_ref = get_edit_bone(leg_ref_name + side)
        if leg_ref:
            init_selection(leg_ref_name + side)
            leg_ref = get_edit_bone(leg_ref_name + side)
            bpy.ops.armature.calculate_roll(type='POS_Z')
            init_selection("null")
            thigh_ref = get_edit_bone(thigh_ref_name + side)
            thigh_ref.select = True
            rig.data.bones.active = rig.pose.bones[leg_ref_name + side].bone
            bpy.ops.armature.calculate_roll(type='ACTIVE')
            if side.endswith(".r"):
                leg_ref.roll += radians(-180)
                thigh_ref.roll += radians(-180)

                
    init_selection("null")
    

    for side in sides:
        # copy the roll to other bones
        leg_ref = get_edit_bone(leg_ref_name + side)
        thigh_ref = get_edit_bone(thigh_ref_name + side)

        if leg_ref and thigh_ref:
            for bname in legs:
                get_edit_bone(bname + side).roll = leg_ref.roll

            for bname in thighs:
                if get_edit_bone(bname + side):
                    get_edit_bone(bname + side).roll = thigh_ref.roll

                    # foot poles
            foot_pole = get_edit_bone(foot_pole_name + side)
            coef = 1
            if side[-2:] == ".r":
                coef = -1
            foot_pole.head = leg_ref.tail + (leg_ref.x_axis * 0.24) * coef * leg_ref.length + leg_ref.y_axis * 0.03 * leg_ref.length
            foot_pole.tail = foot_pole.head + (leg_ref.y_axis * 0.05 * leg_ref.length * 2)
            foot_pole.roll = leg_ref.roll
            
            
    for side in sides:
        leg_ref = get_edit_bone(leg_ref_name + side)
        thigh_ref = get_edit_bone(thigh_ref_name + side)
        
        # stretch controller
        stretch_leg = get_edit_bone(stretch_leg_name + side)       
     
        if stretch_leg:
            if scn.arp_retro_stretch_pin:# backward-compatibility
                dir = stretch_leg.tail - stretch_leg.head
                stretch_leg.head = thigh_ref.tail.copy()
                stretch_leg.tail = stretch_leg.head + dir
            else:
                dir = (thigh_ref.x_axis + leg_ref.x_axis) * 0.5
                if side.endswith('.r'):
                    dir *= -1
                    
                b_len = (thigh_ref.tail-thigh_ref.head).magnitude*0.375
                stretch_leg.head = thigh_ref.tail.copy()
                stretch_leg.tail = stretch_leg.head + (dir.normalized() * b_len)
                align_bone_z_axis(stretch_leg, -thigh_ref.y_axis)
            
            # pin controller
            pin_leg = get_edit_bone(pin_leg_name + side)
            
            if pin_leg:   
                if scn.arp_retro_stretch_pin:# backward-compatibility
                    dir = pin_leg.tail - pin_leg.head
                    pin_leg.head = thigh_ref.tail.copy()
                    pin_leg.tail = pin_leg.head + dir
                else:
                    pin_leg.head = stretch_leg.head.copy()
                    pin_leg.tail = pin_leg.head + (stretch_leg.tail-stretch_leg.head)*0.6            
                    align_bone_z_axis(pin_leg, stretch_leg.z_axis)


    print('  Align feet...')
    # align feet    
    
    for side in sides:
        foot_ref = get_edit_bone(foot_ref_name + side)
        
        for footname in feet:
            current_foot = get_edit_bone(footname + side)
            
            if current_foot:
                if footname == "foot_fk" or footname == "foot_ik" or footname == "foot":
                    copy_bone_transforms(foot_ref, current_foot)           

                if footname == "c_foot_fk" or footname == "c_foot_ik" or footname == "foot_snap_fk" or footname == "c_foot_fk_scale_fix" or footname == "c_foot_ik_offset":                  
                    heel_ref = get_edit_bone('foot_heel_ref' + side)
                    toes_ref = get_edit_bone(toes_ref_name + side)                 
                    current_foot.head = foot_ref.head.copy()

                    len_fac = 3
                    if footname == "c_foot_ik_offset":
                        len_fac = 2.5

                    current_foot.tail = foot_ref.head + (heel_ref.y_axis) * (heel_ref.head - toes_ref.tail).length / len_fac
                    
                    # Transform calculation issue, copying the bone roll may lead to inverted rotation, eventhough the bones have same transforms
                    # To solve this, set the bones roll using axis alignment function
                    align_bone_x_axis(current_foot, heel_ref.x_axis)
                    
                    if scn.arp_retro_feet:# this is incorrect, already mirrored. Still available in option for backward-compatibility
                        current_foot.roll = heel_ref.roll
                        mirror_roll(footname + side, side)

                if footname == "foot_ik_target":
                    current_foot.head = foot_ref.head.copy()
                    current_foot.tail = current_foot.head - (foot_ref.y_axis * 0.05 * foot_ref.length * 6)
                    current_foot.roll = 0

                if "bank" in footname or "foot_heel" in footname:                 
                    heel_ref = get_edit_bone(footname[2:] + "_ref" + side)
                    copy_bone_transforms(heel_ref, current_foot)                  

                if footname == "c_foot_01":
                    current_foot.head = foot_ref.tail.copy()
                    current_foot.tail = current_foot.head + (foot_ref.tail - foot_ref.head) / 2
                    current_foot.roll = foot_ref.roll

     
        # align foot_01_pole
        foot_01_pole = get_edit_bone("foot_01_pole" + side)
        c_foot_01 = get_edit_bone("c_foot_01" + side)

        if current_bone and c_foot_01:
            foot_01_pole.head = c_foot_01.head + (c_foot_01.z_axis * 0.05 * c_foot_01.length * 40)
            foot_01_pole.tail = foot_01_pole.head + (c_foot_01.z_axis * 0.05 * c_foot_01.length * 40)
            foot_01_pole.roll = radians(180)
            mirror_roll("foot_01_pole" + side, side)

        # align foot visual position       
        heel_ref = get_edit_bone(ard.leg_ref_bones_dict['heel'] + side)
        p_foots = ["c_p_foot_ik", "c_p_foot_fk"]

        for p_f in p_foots:
            try:
                p_foot = get_edit_bone(p_f + side)
                copy_bone_transforms(heel_ref, p_foot)              
                p_foot.roll = heel_ref.roll + radians(-90)
                
                if side[-2:] == '.r':
                    p_foot.roll += radians(180)
            except:
                pass
            
            
    print('  Align toes...')
    
    # Align toes
    toes_names = ["c_toes_fk", "c_toes_ik", "toes_01_ik", "c_toes_track", "toes_02", "c_toes_end", "c_toes_end_01", "toes_01"]

    for side in sides:
        toes_ref = get_edit_bone(toes_ref_name + side)
        foot_ref = get_edit_bone(foot_ref_name + side)

        if toes_ref and foot_ref:
            # optional toes_pivot controller
            toes_pivot = get_edit_bone("c_toes_pivot" + side)
            foot_heel_ref = get_edit_bone(ard.leg_ref_bones_dict['heel'] + side)

            if toes_pivot and foot_heel_ref:
                toes_pivot.head = foot_ref.tail.copy()
                toes_pivot.tail = foot_ref.tail - (foot_heel_ref.z_axis.normalized()) * (foot_ref.head - foot_ref.tail).magnitude * 0.5

            # toes bones
            for bname in toes_names:
                if bname == "c_toes_end":
                    current_bone = get_edit_bone(bname + side)
                    current_bone.head = toes_ref.tail.copy()
                    current_bone.tail = current_bone.head + (toes_ref.tail - toes_ref.head) / 2                    
               
                    align_bone_x_axis(current_bone, toes_ref.x_axis)
                    current_bone.roll += radians(180)

                if bname == "c_toes_end_01":
                    current_bone = get_edit_bone(bname + side)
                    current_bone.head = toes_ref.tail.copy()
                    current_bone.tail = current_bone.head + (toes_ref.z_axis * 0.035 * toes_ref.length * 6)
                    current_bone.roll = radians(180)
                    mirror_roll(bname + side, side)

                if bname == "c_toes_fk" or bname == "c_toes_track" or bname == "c_toes_ik":
                    current_bone = get_edit_bone(bname + side)
                    copy_bone_transforms(toes_ref, current_bone)                   
                    current_bone.roll = toes_ref.roll + radians(180)
                    if bname == 'c_toes_track':
                        current_bone.roll += radians(-90)

            if "bname" in locals():
                del bname

    for side in sides:
        for bname in toes_names:
            if bname == "toes_01_ik" or bname == "toes_01":
                if get_edit_bone(bname + side):
                    #init_selection(bname + side)
                    toes_ref = get_edit_bone(toes_ref_name + side)
                    current_bone = get_edit_bone(bname + side)
                    c_toes_fk = get_edit_bone("c_toes_fk" + side)
                    current_bone.head = toes_ref.head.copy()
                    dir = c_toes_fk.tail - c_toes_fk.head
                    current_bone.tail = current_bone.head + dir / 3                    
                   
                    align_bone_x_axis(current_bone, toes_ref.x_axis)
                    current_bone.roll += radians(180)

            # toes_01 must deform only if no individuals toes
            if bname == "toes_01":
                toes_01_bone = get_edit_bone("toes_01" + side)
                toes_finger_found = False
                for ch in toes_ref.children:
                    if ch.name.startswith("toes_thumb") or ch.name.startswith("toes_index") or ch.name.startswith("toes_middle") or ch.name.startswith("toes_ring") or ch.name.startswith("toes_pinky"):
                        toes_finger_found = True
                        break
                if toes_01_bone:
                    if len(toes_ref.children) == 0 or not toes_finger_found:
                        toes_01_bone.use_deform = True
                    else:
                        toes_01_bone.use_deform = False

            if bname == "toes_02":
                if get_edit_bone(bname + side):
                    init_selection(bname + side)
                    toes_ref = get_edit_bone(toes_ref_name + side)
                    toes_01_ik = get_edit_bone("toes_01_ik" + side)
                    current_bone = get_edit_bone(bname + side)
                    c_toes_fk = get_edit_bone("c_toes_fk" + side)
                    current_bone.head = toes_01_ik.tail.copy()
                    current_bone.tail = c_toes_fk.tail.copy()
                  
                    align_bone_x_axis(current_bone, toes_ref.x_axis)
                    current_bone.roll += radians(180)#toes_ref.roll + radians(180)

        if "bname" in locals():
            del bname

    
    print('  Toes fingers...')
    
    #toes_list = ["toes_pinky", "toes_ring", "toes_middle", "toes_index", "toes_thumb"]
    toes_dict = {
        ard.toes_thumb_ref_dict['toes_thumb1']: ard.toes_thumb_control_dict['1'],
        ard.toes_thumb_ref_dict['toes_thumb2']: ard.toes_thumb_control_dict['2'],
        
        ard.toes_index_ref_dict['toes_index1']: ard.toes_index_control_dict['1'],
        ard.toes_index_ref_dict['toes_index2']: ard.toes_index_control_dict['2'],
        ard.toes_index_ref_dict['toes_index3']: ard.toes_index_control_dict['3'],
        
        ard.toes_middle_ref_dict['toes_middle1']: ard.toes_middle_control_dict['1'],
        ard.toes_middle_ref_dict['toes_middle2']: ard.toes_middle_control_dict['2'],
        ard.toes_middle_ref_dict['toes_middle3']: ard.toes_middle_control_dict['3'],
        
        ard.toes_ring_ref_dict['toes_ring1']: ard.toes_ring_control_dict['1'],
        ard.toes_ring_ref_dict['toes_ring2']: ard.toes_ring_control_dict['2'],
        ard.toes_ring_ref_dict['toes_ring3']: ard.toes_ring_control_dict['3'],
        
        ard.toes_pinky_ref_dict['toes_pinky1']: ard.toes_pinky_control_dict['1'],
        ard.toes_pinky_ref_dict['toes_pinky2']: ard.toes_pinky_control_dict['2'],
        ard.toes_pinky_ref_dict['toes_pinky3']: ard.toes_pinky_control_dict['3'],
        }
        
    c_toes_names = []
    fingers_shape_type = rig.arp_fingers_shape_style
    
    for side in sides:
        for toe_ref_name in toes_dict:
            toe_c_name = toes_dict[toe_ref_name]
            toe_ref_bone = get_edit_bone(toe_ref_name+side)            
            toe_c_bone = get_edit_bone(toe_c_name+side)
            
            if toe_ref_bone and toe_c_bone:
                if toe_c_bone.use_deform:
                    copy_bone_transforms(toe_ref_bone, toe_c_bone)
                    c_toes_names.append(toe_c_name+side)
      
    # Set shape
    bpy.ops.object.mode_set(mode='POSE')
    
    for c_toe_name in c_toes_names:
        c_toe_pb = get_pose_bone(c_toe_name)
        # if not a user defined custom shape
        if c_toe_pb.custom_shape:
            if not "cs_user" in c_toe_pb.custom_shape.name:
                if fingers_shape_type == "box":
                    cs_obj = get_object("cs_box")
                if fingers_shape_type == "circle":
                    cs_obj = get_object("cs_torus_04")

                c_toe_pb.custom_shape = cs_obj
                

    bpy.ops.object.mode_set(mode='EDIT')

    
    print("  Foot roll, c_thigh_b...")
    
    leg_twist_dict = {}
    pole_angles_dict = {}

    for side in sides:
        toes_ref = get_edit_bone(toes_ref_name + side)
        heel_ref = get_edit_bone(ard.leg_ref_bones_dict['heel'] + side)

        if toes_ref and heel_ref:
            # foot roll
            c_foot_roll = get_edit_bone("c_foot_roll" + side)
            dist = 1.0
            foot_ref = get_edit_bone(foot_ref_name + side)
            if "roll_cursor_distance" in foot_ref.keys():
                dist = foot_ref["roll_cursor_distance"]

            c_foot_roll.head = heel_ref.head - heel_ref.y_axis * (toes_ref.head - toes_ref.tail).length * 2 * dist
            c_foot_roll.tail = c_foot_roll.head - heel_ref.y_axis * (toes_ref.head - toes_ref.tail).length * 0.6
            bpy.ops.armature.select_all(action='DESELECT')
            rig.data.edit_bones.active = c_foot_roll
            rig.data.edit_bones.active = toes_ref
            bpy.ops.armature.calculate_roll(type='ACTIVE')
            c_foot_roll.roll += radians(-90 + 180)

            # cursor bank roll
            c_foot_roll_cursor = get_edit_bone("c_foot_roll_cursor" + side)
            c_foot_roll_cursor.head = c_foot_roll.tail - (c_foot_roll.x_axis * c_foot_roll.length)

            c_foot_roll_cursor.tail = c_foot_roll_cursor.head - (c_foot_roll.tail - c_foot_roll.head)
            bpy.ops.armature.select_all(action='DESELECT')
            rig.data.edit_bones.active = c_foot_roll_cursor
            rig.data.edit_bones.active = toes_ref
            bpy.ops.armature.calculate_roll(type='ACTIVE')
            c_foot_roll_cursor.roll += radians(-90 + 180)

            if side[-2:] == '.r':
                c_foot_roll_cursor.roll += radians(180)

            # align c_thigh_b
            c_thigh_b = get_edit_bone("c_thigh_b" + side)
            thigh_fk = get_edit_bone("thigh_fk" + side)

            # 3 bones leg case: if thigh_b has a reference bone, use it
            thigh_b_ref = get_edit_bone(thigh_b_ref_name + side)

            if thigh_b_ref:
                if thigh_b_ref.parent:
                    c_thigh_b.parent = parent_retarget(thigh_b_ref)

                copy_bone_transforms(thigh_b_ref, c_thigh_b)
                set_3_bones_ik_chain(pole_angles_dict, side)

            # 2 bones leg case
            else:
                thigh_ref = get_edit_bone(thigh_ref_name + side)
                if thigh_ref.parent:
                    c_thigh_b.parent = parent_retarget(thigh_ref)
                else:
                    c_thigh_b.parent = get_edit_bone(get_first_master_controller())

                dir = thigh_fk.tail - thigh_fk.head
                c_thigh_b.head = thigh_fk.head - dir / 7
                c_thigh_b.tail = thigh_fk.head
                c_thigh_b.roll = thigh_fk.roll

                # remove 3 legs ik chain
                unset_3_bones_ik_chain(side)

        # setup twist bones
        # get leg twist amount
        twist_bones_amount = 1
        thighb = get_edit_bone(thigh_ref_name + side)

        if rig.arp_secondary_type != "BENDY_BONES":# no twist bones when using bendy bones
            if len(thighb.keys()) > 0:
                if "twist_bones_amount" in thighb.keys():# backward-compatibility
                    twist_bones_amount = thighb["twist_bones_amount"]

        leg_twist_dict[side] = twist_bones_amount

        # set twist function
        set_leg_twist(twist_bones_amount, side)

        
    #print('  Switch mode')
   
    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.mode_set(mode='POSE')

    
    for side in sides:
    
        # third bone leg shape, 3 bones
        thigh_b_ref = get_pose_bone(thigh_b_ref_name + side)
        c_thigh_b = get_pose_bone("c_thigh_b" + side)
        if thigh_b_ref:
            set_custom_shape = False

            if c_thigh_b.custom_shape == None:
                set_custom_shape = True
            else:
                if not c_thigh_b.custom_shape.name.startswith("cs_user"):
                    set_custom_shape = True
            if set_custom_shape:
                c_thigh_b.custom_shape = get_object("cs_box")

            # set IK constraints
            set_3_bones_constraint(pole_angles_dict, side)

        else:
            # unset IK constraints
            unset_3_bones_ik_constraint(side)

            # third bone leg shape, 2 bones
            set_custom_shape = False
            if c_thigh_b.custom_shape == None:
                set_custom_shape = True
            else:
                if not c_thigh_b.custom_shape.name.startswith("cs_user"):
                    set_custom_shape = True
            if set_custom_shape:
                c_thigh_b.custom_shape = get_object("cs_curve")
                
                
        # set default IK-FK switch value
        c_foot_ik = get_pose_bone(c_foot_ik_name+side)
        default_IKFK = legs_ikfk_default_dict[side]
        default_val =  0.0 if default_IKFK == 'IK' else 1.0
        set_prop_setting(c_foot_ik, 'ik_fk_switch', 'default', default_val)       
      
        # Leg IK stretch value reset
        thigh_ik_p = get_pose_bone("thigh_ik" + side)
        leg_ik_p = get_pose_bone("leg_ik" + side)
        
        # Backward-compatibility, Blender 2.81 and before do not support Swing setting for stretch to constraints

        if thigh_ik_p and leg_ik_p:
        
            thigh_ik_length = thigh_ik_p.length
            leg_ik_length = leg_ik_p.length

            if thigh_ik_length < leg_ik_length:
                thigh_ik_p.ik_stretch = (thigh_ik_length ** (1 / 3)) / (leg_ik_length ** (1 / 3))
                leg_ik_p.ik_stretch = 1.0
            else:
                thigh_ik_p.ik_stretch = 1.0
                leg_ik_p.ik_stretch = (leg_ik_length ** (1 / 3)) / (thigh_ik_length ** (1 / 3))                
            

            def remove_twist_based_bendy(side):
                bpy.ops.object.mode_set(mode='EDIT')
                # remove bendy bones
                leg_bendy_name = "leg_bendy" + side
                leg_bendy = get_edit_bone(leg_bendy_name)
                thigh_bendy_name = "thigh_bendy" + side
                thigh_bendy = get_edit_bone(thigh_bendy_name)
                if leg_bendy:
                    delete_edit_bone(leg_bendy)
                if thigh_bendy:
                    delete_edit_bone(thigh_bendy)

            def remove_twist_based_segments(side):
                bpy.ops.object.mode_set(mode='EDIT')
                for idx in range(1, 33):
                    for leg_limb in ["leg", "thigh"]:
                        bone_segment = get_edit_bone(leg_limb + "_" + "segment_" + str(idx) + side)
                        if bone_segment:
                            delete_edit_bone(bone_segment)

            def remove_twist_based_constraints(side):
                bpy.ops.object.mode_set(mode='POSE')

                for leg_limb in ["leg", "thigh"]:
                    for idx in range(1, 33):
                        twist_idx = "_" + str(idx)
                        if idx == 1:
                            twist_idx = ""
                        # twist constraints
                        twist_pbone = get_pose_bone(leg_limb + "_twist" + twist_idx + side)
                        if twist_pbone == None:
                            continue

                        cns_loc = twist_pbone.constraints.get("Copy Location_wrap")
                        if cns_loc:
                            twist_pbone.constraints.remove(cns_loc)

                        cns_damped = twist_pbone.constraints.get("Damped Track_wrap")
                        if cns_damped:
                            twist_pbone.constraints.remove(cns_damped)

            def remove_twist_based_str(side):
                bpy.ops.object.mode_set(mode='EDIT')

                for leg_limb in ["leg", "thigh"]:
                    leg_str_offset = get_edit_bone(leg_limb + "_str_offset" + side)
                    if leg_str_offset:
                        delete_edit_bone(leg_str_offset)

                    leg_twt_offset = get_edit_bone(leg_limb + "_twt_offset" + side)
                    if leg_twt_offset:
                        delete_edit_bone(leg_twt_offset)

            if get_pose_bone("thigh_stretch" + side) == None:
                continue

            print("  Set secondary controllers ["+side+"]")
            drivers_list = rig.animation_data.drivers

            # generate the twist bones list
            twist_bones_amount = leg_twist_dict[side]
            twist_bones_list = []

            for leg_type in ['thigh', 'leg']:
                for twist_idx in range(1, twist_bones_amount + 1):
                    str_idx = '_' + str(twist_idx)
                    if twist_idx == 1:
                        str_idx = ''# the first twist bone has no id by convention
                    twist_bones_list.append(leg_type + '_twist' + str_idx + side)

                # add the stretch bone to the list
                twist_bones_list.append(leg_type + '_stretch' + side)


            # 1.Bendy bones
            if rig.arp_secondary_type == "BENDY_BONES":

                bpy.ops.object.mode_set(mode='EDIT')

                secondary_list_remove = ['c_thigh_bend_contact', 'c_knee_bend', 'c_ankle_bend']
                secondary_list = {'c_thigh_bend_01':"thigh_stretch", 'c_thigh_bend_02':"thigh_stretch", 'c_leg_bend_01': "leg_stretch", 'c_leg_bend_02': "leg_stretch"}
                created_bones = []

                # Remove secondary controllers
                for bn in secondary_list_remove:
                    ebn = get_edit_bone(bn+side)
                    if ebn:
                        delete_edit_bone(ebn)

                    # proxy
                for bn in secondary_list_remove:
                    ebn_proxy = get_edit_bone(bn+'_proxy'+side)
                    if ebn_proxy:
                        delete_edit_bone(ebn_proxy)


                # create missing secondary controllers
                for bn in secondary_list:
                    eb = get_edit_bone(bn+side)
                    if eb == None:
                        eb = rig.data.edit_bones.new(bn+side)
                        # set layer
                        set_bone_layer(eb, 1)
                        created_bones.append(eb.name)

                    # set deform
                    eb.use_deform = False
                    # set parents
                    eb.parent = get_edit_bone(secondary_list[bn]+side)
                    # set visibility
                    eb.hide = False

                align_leg_bend_bones(side)


                thigh_stretch = get_edit_bone("thigh_stretch" + side)
                leg_stretch = get_edit_bone("leg_stretch" + side)

                # get bbones ease out driven state
                thigh_ref = get_edit_bone(thigh_ref_name + side)
                leg_bbones_ease_out = thigh_ref.get("arp_bbones_ease_out")
                set_ease_out_driver = True
                if leg_bbones_ease_out != None:
                    set_ease_out_driver = leg_bbones_ease_out

                # get bones lengths
                thigh_length = thigh_stretch.length
                leg_length = leg_stretch.length

                # enable stretch deform
                thigh_stretch.use_deform = True
                leg_stretch.use_deform = True

                bpy.ops.object.mode_set(mode='POSE')

                for bn in created_bones:
                    pbn = get_pose_bone(bn)
                    # set rot mode
                    pbn.rotation_mode = "XYZ"
                    # set custom shape
                    set_bone_custom_shape(pbn, "cs_torus_01")
                    # set bone group
                    pbn.bone_group = rig.pose.bone_groups.get('body'+side[-2:])
                    # set visibility
                    pbn.bone.hide = False


                # constraints
                cns = get_pose_bone("thigh_stretch" + side).constraints.get("Copy Location")
                if cns:  # backward-compatibility
                    cns.head_tail = 0.0

                # disable twist deform
                get_pose_bone("thigh_twist" + side).bone.use_deform = False
                get_pose_bone("leg_twist" + side).bone.use_deform = False

                # custom handles
                thigh_stretch_pbone = get_pose_bone("thigh_stretch" + side)
                leg_stretch_pbone = get_pose_bone("leg_stretch" + side)
                thigh_stretch_pbone.bone.bbone_handle_type_start = "ABSOLUTE"
                thigh_stretch_pbone.bone.bbone_handle_type_end = "ABSOLUTE"
                leg_stretch_pbone.bone.bbone_handle_type_start = "AUTO"  # Absolute leads to slightly bend the first bbones, set it to Automatic instead
                leg_stretch_pbone.bone.bbone_handle_type_end = "TANGENT"

                thigh_stretch_pbone.bone.bbone_custom_handle_start = get_pose_bone("c_thigh_b" + side).bone

                thigh_stretch_pbone.bone.bbone_custom_handle_end = leg_stretch_pbone.bone

                leg_stretch_pbone.bone.bbone_custom_handle_start = thigh_stretch_pbone.bone

                if set_ease_out_driver:
                    leg_stretch_pbone.bone.bbone_custom_handle_end = get_pose_bone(
                        "foot_pole" + side).bone

                # Set the drivers
                # thigh bones
                set_secondary_drivers(drivers_list, ['thigh_stretch', 'c_thigh_bend_01', 'c_thigh_bend_02'], side, thigh_length)

                # leg bones
                set_secondary_drivers(drivers_list, ['leg_stretch', 'c_leg_bend_01', 'c_leg_bend_02'], side, thigh_length, enable_ease_out_dr=set_ease_out_driver)

                # remove any unwanted twist controllers
                # twist bones amount is automatically set to 1 for bendy bones, then iterate over the max range 1-32
                twist_bones_list = []
                for leg_type in ['thigh', 'leg']:
                    for twist_idx in range(1, 33):
                        str_idx = '_' + str(twist_idx)
                        if twist_idx == 1:
                            str_idx = ''  # the first twist bone has no id by convention
                        twist_bones_list.append(leg_type + '_twist' + str_idx + side)

                    # add the stretch bone to the list
                    twist_bones_list.append(leg_type + '_stretch' + side)

                bpy.ops.object.mode_set(mode='EDIT')

                for bname in twist_bones_list:
                    b_twist = get_edit_bone(bname)
                    c_twist_name = 'c_' + bname
                    c_twist = get_edit_bone(c_twist_name)
                    # remove
                    if c_twist:
                        delete_edit_bone(c_twist)

                remove_twist_based_constraints(side)
                remove_twist_based_segments(side)
                remove_twist_based_bendy(side)
                remove_twist_based_str(side)

                bpy.ops.object.mode_set(mode='POSE')


            # 2.Additive mode
            elif rig.arp_secondary_type == "ADDITIVE":

                bpy.ops.object.mode_set(mode='EDIT')

                secondary_list = {'c_thigh_bend_contact':'thigh_twist', 'c_thigh_bend_01':"thigh_twist", 'c_thigh_bend_02':'thigh_stretch', 'c_knee_bend':'leg_stretch', 'c_leg_bend_01': "leg_stretch", 'c_leg_bend_02': "leg_stretch", 'c_ankle_bend':'leg_twist'}
                created_bones = []

                # create missing secondary controllers
                for bn in secondary_list:
                    eb = get_edit_bone(bn+side)
                    if eb == None:
                        eb = rig.data.edit_bones.new(bn+side)
                        # set layer
                        set_bone_layer(eb, 1)
                        created_bones.append(eb.name)

                    # set deform
                    eb.use_deform = False
                    # set parents
                    eb.parent = get_edit_bone(secondary_list[bn]+side)
                    # set visibility
                    eb.hide = False

                align_leg_bend_bones(side)

                bpy.ops.object.mode_set(mode='POSE')

                for bn in created_bones:
                    pbn = get_pose_bone(bn)
                    # set rot mode
                    pbn.rotation_mode = "XYZ"
                    # set custom shape
                    set_bone_custom_shape(pbn, "cs_torus_01")
                    # set bone group
                    pbn.bone_group = rig.pose.bone_groups.get('body'+side[-2:])
                    # set visibility
                    pbn.bone.hide = False


                # custom handles
                thigh_stretch_pb = get_pose_bone("thigh_stretch" + side)
                leg_stretch_pb = get_pose_bone("leg_stretch" + side)

                thigh_stretch_pb.bone.bbone_handle_type_start = 'AUTO'
                thigh_stretch_pb.bone.bbone_handle_type_end = 'AUTO'
                leg_stretch_pb.bone.bbone_handle_type_start = 'AUTO'
                leg_stretch_pb.bone.bbone_handle_type_end = 'AUTO'

                # constraints
                cns = get_pose_bone("thigh_stretch" + side).constraints.get("Copy Location")
                if cns:  # backward-compatibility
                    cns.head_tail = 1.0

                # Set twist deform and rig_add bend deform
                # in Additive mode, secondary controllers deform
                thigh_ik = get_pose_bone("c_thigh_ik" + side)
                if thigh_ik:
                    if thigh_ik.bone.layers[22] == False:  # if not disabled
                        thigh_twist = get_pose_bone("thigh_twist" + side)
                        if thigh_twist:
                            thigh_twist.bone.use_deform = True

                        leg_twist = get_pose_bone("leg_twist" + side)
                        if leg_twist:
                            leg_twist.bone.use_deform = True

                        _rig_add = get_rig_add(bpy.context.active_object)
                        if _rig_add:
                            for add_bname in ard.leg_bones_rig_add:
                                b = _rig_add.data.bones.get(add_bname + side)
                                if b:
                                    b.use_deform = True

                else:
                    print("  c_thigh_ik" + side + " not found")


                # Set twist controllers
                # if Additive mode, remove any additional twist controllers
                bpy.ops.object.mode_set(mode='EDIT')
                for bname in twist_bones_list:
                    b_twist = get_edit_bone(bname)
                    c_twist_name = 'c_' + bname
                    c_twist = get_edit_bone(c_twist_name)
                    # remove
                    if c_twist:
                        delete_edit_bone(c_twist)
                    # enable base twist bone deform
                    b_twist.use_deform = True

                remove_twist_based_constraints(side)
                remove_twist_based_segments(side)
                remove_twist_based_bendy(side)
                remove_twist_based_str(side)

                bpy.ops.object.mode_set(mode='POSE')


            # 3.Twist mode
            elif rig.arp_secondary_type == "TWIST_BASED":
                
                secondary_list_remove = ['c_thigh_bend_contact', 'c_knee_bend', 'c_ankle_bend']
                secondary_list = {'c_thigh_bend_01':"thigh_stretch", 'c_thigh_bend_02':"thigh_stretch", 'c_leg_bend_01': "leg_stretch", 'c_leg_bend_02': "leg_stretch"}
                created_bones = []

                bpy.ops.object.mode_set(mode='EDIT')

                # Remove secondary controllers
                for bn in secondary_list_remove:
                    ebn = get_edit_bone(bn+side)
                    if ebn:
                        delete_edit_bone(ebn)

                    # proxy
                for bn in secondary_list_remove:
                    ebn_proxy = get_edit_bone(bn+'_proxy'+side)
                    if ebn_proxy:
                        delete_edit_bone(ebn_proxy)


                # create missing secondary controllers
                for bn in secondary_list:
                    eb = get_edit_bone(bn+side)
                    if eb == None:
                        eb = rig.data.edit_bones.new(bn+side)
                        # set layer
                        set_bone_layer(eb, 1)
                        created_bones.append(eb.name)

                    # set deform
                    eb.use_deform = False
                    # set parents
                    eb.parent = get_edit_bone(secondary_list[bn]+side)
                    # set visibility
                    eb.hide = False

                align_leg_bend_bones(side)

                bpy.ops.object.mode_set(mode='POSE')
                
                #   ensure bend controllers are in color groups
                for bn in secondary_list:
                    pbn = get_pose_bone(bn+side)
                    if pbn.bone_group == None:
                        pbn.bone_group = rig.pose.bone_groups.get('body'+side[-2:])
                    
                
                #   set bones params
                for bn in created_bones:
                    pbn = get_pose_bone(bn)
                    # set rot mode
                    pbn.rotation_mode = "XYZ"
                    # set custom shape
                    set_bone_custom_shape(pbn, "cs_torus_01")                    
                    # set visibility
                    pbn.bone.hide = False


                # custom handles
                thigh_stretch_pb = get_pose_bone("thigh_stretch" + side)
                leg_stretch_pb = get_pose_bone("leg_stretch" + side)

                thigh_stretch_pb.bone.bbone_handle_type_start = 'AUTO'
                thigh_stretch_pb.bone.bbone_handle_type_end = 'AUTO'
                leg_stretch_pb.bone.bbone_handle_type_start = 'AUTO'
                leg_stretch_pb.bone.bbone_handle_type_end = 'AUTO'

                # constraints
                cns = get_pose_bone("thigh_stretch" + side).constraints.get("Copy Location")
                if cns:  # backward-compatibility
                    cns.head_tail = 1.0

                # Set twist deform and rig_add bend deform
                # in Twist Based mode, secondary controllers don't
                thigh_ik = get_pose_bone("c_thigh_ik" + side)
                if thigh_ik.bone.layers[22] == False:  # if not disabled
                    thigh_twist = get_pose_bone("thigh_twist" + side)
                    if thigh_twist:
                        thigh_twist.bone.use_deform = False  # twist is replaced by the first c_twist bone deformation

                    leg_twist = get_pose_bone("leg_twist" + side)
                    if leg_twist:
                        leg_twist.bone.use_deform = True


                bpy.ops.object.mode_set(mode='EDIT')

                # Set twist controllers
                # delete unwanted controllers bones
                for idx in range(twist_bones_amount + 1, 33):
                    for blimb in ['thigh', 'leg']:
                        c_twist_to_del = get_edit_bone("c_" + blimb + "_twist_" + str(idx) + side)
                        if c_twist_to_del:
                            delete_edit_bone(c_twist_to_del)

                # add new offset bones
                for leg in ['thigh', 'leg']:
                    # create an offset bone for the leg stretch bone, to preserve the stretch bone rotation when curving the twist bones
                    str_offset_name = leg + "_str_offset" + side
                    leg_str_offset = get_edit_bone(str_offset_name)
                    if leg_str_offset == None:
                        leg_str_offset = rig.data.edit_bones.new(str_offset_name)
                    leg_stretch = get_edit_bone(leg + "_stretch" + side)
                    # set coords
                    leg_str_offset.head, leg_str_offset.tail, leg_str_offset.roll = leg_stretch.head.copy(), leg_stretch.tail.copy(), leg_stretch.roll
                    # set parent
                    leg_str_offset.parent = leg_stretch
                    # set layers
                    set_bone_layer(leg_str_offset, 9)
                    # set deform
                    leg_str_offset.use_deform = False
                    # replace it in the list
                    index_in_list = twist_bones_list.index(leg + "_stretch" + side)
                    twist_bones_list.pop(index_in_list)
                    twist_bones_list.insert(index_in_list, leg + "_str_offset" + side)

                    # create an offset bone for the thigh_twist bone, to preserve the stretch bone rotation when curving the twist bones
                    if leg == "thigh":
                        twist_offset_name = leg + "_twt_offset" + side
                        twist_offset = get_edit_bone(twist_offset_name)
                        if twist_offset == None:
                            twist_offset = rig.data.edit_bones.new(twist_offset_name)
                        thigh_twist = get_edit_bone(leg + "_twist" + side)
                        # set coords
                        twist_offset.head, twist_offset.tail, twist_offset.roll = thigh_twist.head.copy(), thigh_twist.tail.copy(), thigh_twist.roll
                        # set parent
                        twist_offset.parent = thigh_twist
                        # set layers
                        set_bone_layer(twist_offset, 9)
                        # set deform
                        twist_offset.use_deform = False
                        # replace it in the list
                        index_in_list = twist_bones_list.index(leg + "_twist" + side)
                        twist_bones_list.pop(index_in_list)
                        twist_bones_list.insert(index_in_list, leg + "_twt_offset" + side)

                # create the twist controllers
                bpy.ops.object.mode_set(mode='EDIT')
                
                c_twist_names = []
                
                for bname in twist_bones_list:
                    b_twist = get_edit_bone(bname)
                    base_stretch = None
                    c_twist_name = 'c_' + bname
                    if "_str_offset" in bname:  # exception, stretch offset case
                        base_stretch = get_edit_bone(bname.replace("_str_offset", "_stretch"))
                        c_twist_name = c_twist_name.replace("_str_offset", "_stretch")
                    if "_twt_offset" in bname:  # exception, twist offset case
                        c_twist_name = c_twist_name.replace("_twt_offset", "_twist")

                    c_twist = get_edit_bone(c_twist_name)
                    # create the bone
                    if c_twist == None:
                        c_twist = rig.data.edit_bones.new(c_twist_name)

                    c_twist_names.append(c_twist_name)
                    
                    # set coords
                    c_twist.head, c_twist.tail, c_twist.roll = b_twist.head.copy(), b_twist.tail.copy(), b_twist.roll
                    # disable base twist bones deform
                    b_twist.use_deform = False
                    # enable c_twist bone deform
                    c_twist.use_deform = True
                    # set parent
                    c_twist.parent = b_twist
                    # set layers
                    set_bone_layer(c_twist, 1)
                    # the base stretch bone must not deform
                    if base_stretch:
                        base_stretch.use_deform = False

                        
                bpy.ops.object.mode_set(mode='POSE')

                for c_twist_name in c_twist_names:                 
                    c_twist_pb = get_pose_bone(c_twist_name)
                    # set rotation mode
                    c_twist_pb.rotation_mode = "XYZ"
                    # set bone shape
                    twist_shape = get_object("cs_twist_shape")
                    if twist_shape == None:
                        append_from_arp(nodes=["cs_twist_shape"], type="object")

                    set_custom_shape = True

                    if c_twist_pb.custom_shape != None:
                        if c_twist_pb.custom_shape.name.startswith("cs_user_"):
                            set_custom_shape = False

                    if set_custom_shape:
                        c_twist_pb.custom_shape = get_object("cs_twist_shape")

                        if twist_bones_amount < 7:#backward-compatibility, twist_bones_amount was limited to 6
                            set_custom_shape_scale(c_twist_pb, (1 / (10 - twist_bones_amount)) * 4)
                        else:
                            set_custom_shape_scale(c_twist_pb, twist_bones_amount/6)

                    # set bone group
                    if c_twist_pb.bone_group == None:                        
                        c_twist_pb.bone_group = rig.pose.bone_groups.get('body' + side[-2:])
                        

                # Add a bendy bone for easy curvature control of the twist bones + add segment bones wrapped to it
                for leg in ['thigh', 'leg']:

                    bpy.ops.object.mode_set(mode='EDIT')

                    # Bendy Bone
                    bendy_bone_name = leg + "_bendy" + side
                    bendy_bone = get_edit_bone(bendy_bone_name)
                    if bendy_bone == None:
                        bendy_bone = rig.data.edit_bones.new(bendy_bone_name)
                    leg_ebone = get_edit_bone(leg + side)
                    # set coords
                    bendy_bone.head, bendy_bone.tail, bendy_bone.roll = leg_ebone.head.copy(), leg_ebone.tail.copy(), leg_ebone.roll
                    bendy_bone.bbone_segments = twist_bones_amount + 1
                    leg_length = bendy_bone.length
                    # set parent
                    bendy_bone.parent = get_edit_bone(get_first_master_controller())
                    # set layers
                    set_bone_layer(bendy_bone, 9)
                    # set deformation
                    bendy_bone.use_deform = False

                    # bendy bone: set constraints
                    bpy.ops.object.mode_set(mode='POSE')

                    bendy_bone_pbone = get_pose_bone(bendy_bone_name)

                    cns_loc = bendy_bone_pbone.constraints.get("Copy Location")
                    if cns_loc == None:
                        cns_loc = bendy_bone_pbone.constraints.new("COPY_LOCATION")
                    cns_loc.name = "Copy Location"
                    cns_loc.target = rig
                    if leg == "leg":
                        cns_loc.subtarget = "c_stretch_leg" + side
                    elif leg == "thigh":
                        cns_loc.subtarget = "thigh" + side

                    cns_rot = bendy_bone_pbone.constraints.get("Copy Rotation")
                    if cns_rot == None:
                        cns_rot = bendy_bone_pbone.constraints.new("COPY_ROTATION")
                    cns_rot.name = "Copy Rotation"
                    cns_rot.target = rig
                    cns_rot.subtarget = leg + side

                    cns_stretch = bendy_bone_pbone.constraints.get("Stretch To")
                    if cns_stretch == None:
                        cns_stretch = bendy_bone_pbone.constraints.new("STRETCH_TO")
                    cns_stretch.name = "Stretch To"
                    cns_stretch.target = rig
                    if leg == "leg":
                        cns_stretch.subtarget = "foot" + side
                    elif leg == "thigh":
                        cns_stretch.subtarget = "c_stretch_leg" + side
                    cns_stretch.volume = "NO_VOLUME"

                    # bendy bone: set drivers
                    drivers_list = rig.animation_data.drivers

                    if leg == "leg":
                        set_secondary_drivers(drivers_list, ['leg_bendy', 'c_leg_bend_01', 'c_leg_bend_02'], side,
                                              leg_length)
                    elif leg == "thigh":
                        set_secondary_drivers(drivers_list, ['thigh_bendy', 'c_thigh_bend_01', 'c_thigh_bend_02'], side,
                                              leg_length)

                        # Bones Segments
                    bpy.ops.object.mode_set(mode='EDIT')

                    # delete unwanted bones segments
                    for idx in range(twist_bones_amount + 1, 33):
                        bone_segment = get_edit_bone(leg + "_" + "segment_" + str(idx) + side)

                        # the thigh bone has an extra segment, keep it
                        if leg == "thigh" and idx == twist_bones_amount + 1:
                            continue

                        if bone_segment:
                            delete_edit_bone(bone_segment)

                    # add bones segments
                    segments_names = {}
                    
                    for idx in range(1, twist_bones_amount + 1):
                        bone_segment_name = leg + "_segment_" + str(idx) + side
                        bone_segment = get_edit_bone(bone_segment_name)
                        if bone_segment == None:
                            bone_segment = rig.data.edit_bones.new(bone_segment_name)                            
                        
                        # set coords
                        twist_bone_name = leg + "_twist_" + str(idx) + side
                        if idx == 1:
                            twist_bone_name = leg + "_twist" + side
                        twist_bone = get_edit_bone(twist_bone_name)
                        bone_segment.head = twist_bone.head.copy()
                        bone_segment.tail = bone_segment.head + (-twist_bone.z_axis.normalized() * (twist_bone.tail - twist_bone.head).magnitude)
                        bone_segment.roll = 0.0
                        # parent
                        bone_segment.parent = get_edit_bone(bendy_bone_name)
                        # set layers
                        set_bone_layer(bone_segment, 11)
                        # set deform
                        bone_segment.use_deform = False
                        
                        segments_names[bone_segment_name] = idx
                        
                        if leg == "thigh" and idx == twist_bones_amount:  # an extra segment bone must be added for the last twist bone of the thigh                         
                            bone_segment_name = leg + "_segment_" + str(idx + 1) + side
                            bone_segment = get_edit_bone(bone_segment_name)
                            if bone_segment == None:
                                bone_segment = rig.data.edit_bones.new(bone_segment_name)
                                
                            # set coords
                            twist_bone = get_edit_bone(twist_bone_name)
                            bone_segment.head = twist_bone.tail.copy()
                            bone_segment.tail = bone_segment.head + (-twist_bone.z_axis.normalized() * (twist_bone.tail - twist_bone.head).magnitude)
                            bone_segment.roll = 0.0
                            # parent
                            bone_segment.parent = get_edit_bone(bendy_bone_name)
                            # set layers
                            set_bone_layer(bone_segment, 11)
                            # set deform
                            bone_segment.use_deform = False                          
                    
                    bpy.ops.object.mode_set(mode='POSE')
                    
                    # set constraints
                    for bone_segment_name in segments_names:
                        idx = segments_names[bone_segment_name]                        
                        bone_segment_pbone = get_pose_bone(bone_segment_name)
                        
                        cns = bone_segment_pbone.constraints.get("Copy Location")                        
                        if cns == None:
                            cns = bone_segment_pbone.constraints.new("COPY_LOCATION")
                            
                        cns.name = "Copy Location"
                        cns.target = rig
                        cns.subtarget = bendy_bone_name
                        if leg == "thigh":
                            cns.head_tail = (1 / (twist_bones_amount + 1)) * (idx - 1)
                        elif leg == "leg":
                            cns.head_tail = 1 - (idx / (twist_bones_amount + 1))

                        cns.use_bbone_shape = True

                        if leg == "thigh" and idx == twist_bones_amount:# extra segment bone for the last twist bone of the thigh
                            bone_segment_name = leg + "_segment_" + str(idx + 1) + side
                            bone_segment_pbone = get_pose_bone(bone_segment_name)
                            cns = bone_segment_pbone.constraints.get("Copy Location")
                            if cns == None:
                                cns = bone_segment_pbone.constraints.new("COPY_LOCATION")
                            cns.name = "Copy Location"
                            cns.target = rig
                            cns.subtarget = bendy_bone_name
                            cns.head_tail = (1 / (twist_bones_amount + 1)) * (idx)
                            cns.use_bbone_shape = True
                            

                    # wrap twist bones on bone segments
                    for idx in range(1, twist_bones_amount + 1):
                        twist_idx = "_" + str(idx)
                        if idx == 1:
                            twist_idx = ""

                        twist_pbone = get_pose_bone(leg + "_twist" + twist_idx + side)

                        # add loc constraint
                        cns_loc = twist_pbone.constraints.get("Copy Location_wrap")
                        if cns_loc == None:
                            cns_loc = twist_pbone.constraints.new("COPY_LOCATION")
                        cns_loc.name = "Copy Location_wrap"
                        cns_loc.target = rig
                        cns_loc.subtarget = leg + "_segment_" + str(idx) + side

                        if leg == "leg":
                            # add damped track constraints
                            if idx != 1:  # the first twist bone has already a Stretch To constraint to the foot
                                cns_damped = twist_pbone.constraints.get("Damped Track_wrap")
                                if cns_damped == None:
                                    cns_damped = twist_pbone.constraints.new("DAMPED_TRACK")
                                cns_damped.name = "Damped Track_wrap"
                                cns_damped.target = rig
                                cns_damped.subtarget = "leg" + "_segment_" + str(idx - 1) + side
                            else:
                                # the StretchTo constraint must be last in the stack, delete it then add it
                                stretch_cns = twist_pbone.constraints.get("Stretch To")
                                if stretch_cns:
                                    twist_pbone.constraints.remove(stretch_cns)
                                stretch_cns = twist_pbone.constraints.new("STRETCH_TO")
                                stretch_cns.name = "Stretch To"
                                stretch_cns.target = rig
                                stretch_cns.subtarget = "foot" + side
                                stretch_cns.volume = "NO_VOLUME"

                            # at last, setup the stretch bone constraint
                            # must point toward the last bone segment
                            if idx == twist_bones_amount:
                                c_stretch = get_pose_bone("leg" + "_str_offset" + side)
                                cns_damped = c_stretch.constraints.get("Damped Track_wrap")
                                if cns_damped == None:
                                    cns_damped = c_stretch.constraints.new("DAMPED_TRACK")
                                cns_damped.name = "Damped Track_wrap"
                                cns_damped.target = rig
                                cns_damped.subtarget = "leg" + "_segment_" + str(idx) + side

                        elif leg == "thigh":
                            if idx == 1:
                                arm_twt_offset = get_pose_bone("thigh_twt_offset" + side)
                                # damped track
                                cns_damp = arm_twt_offset.constraints.get("Damped Track_wrap")
                                if cns_damp == None:
                                    cns_damp = arm_twt_offset.constraints.new("DAMPED_TRACK")
                                cns_damp.name = "Damped Track_wrap"
                                cns_damp.target = rig
                                cns_damp.subtarget = "thigh" + "_segment_" + str(idx + 1) + side

                            # add damped track
                            else:
                                cns_damped = twist_pbone.constraints.get("Damped Track_wrap")
                                if cns_damped == None:
                                    cns_damped = twist_pbone.constraints.new("DAMPED_TRACK")
                                cns_damped.name = "Damped Track_wrap"
                                cns_damped.target = rig
                                cns_damped.subtarget = "thigh" + "_segment_" + str(idx + 1) + side

                            if idx == twist_bones_amount:
                                # at last add constraints to the stretch bone of the arm
                                c_stretch = get_pose_bone("thigh" + "_str_offset" + side)
                                # loc
                                cns_loc = c_stretch.constraints.get("Copy Location_wrap")
                                if cns_loc == None:
                                    cns_loc = c_stretch.constraints.new("COPY_LOCATION")
                                cns_loc.name = "Copy Location_wrap"
                                cns_loc.target = rig
                                cns_loc.subtarget = "thigh" + "_segment_" + str(idx + 1) + side

                                # damped track
                                cns_damped = c_stretch.constraints.get("Damped Track_wrap")
                                if cns_damped == None:
                                    cns_damped = c_stretch.constraints.new("DAMPED_TRACK")
                                cns_damped.name = "Damped Track_wrap"
                                cns_damped.target = rig
                                cns_damped.subtarget = "c_stretch_leg" + side


            # 4.None mode
            elif rig.arp_secondary_type == "NONE":

                bpy.ops.object.mode_set(mode='POSE')

                # custom handles
                thigh_stretch_pb = get_pose_bone("thigh_stretch" + side)
                leg_stretch_pb = get_pose_bone("leg_stretch" + side)

                thigh_stretch_pb.bone.bbone_handle_type_start = 'AUTO'
                thigh_stretch_pb.bone.bbone_handle_type_end = 'AUTO'
                leg_stretch_pb.bone.bbone_handle_type_start = 'AUTO'
                leg_stretch_pb.bone.bbone_handle_type_end = 'AUTO'

                # constraints
                cns = thigh_stretch_pb.constraints.get("Copy Location")
                if cns:  # backward-compatibility
                    cns.head_tail = 1.0

                # enable twist deform
                thigh_ik = get_pose_bone("c_thigh_ik" + side)
                thigh_twist = get_pose_bone("thigh_twist" + side)
                leg_twist = get_pose_bone("leg_twist" + side)
                if thigh_ik:
                    if thigh_ik.bone.layers[22] == False:  # if not disabled
                        thigh_twist.bone.use_deform = True
                        leg_twist.bone.use_deform = True


                bpy.ops.object.mode_set(mode='EDIT')

                # Remove secondary controllers
                secondary_list = ['c_thigh_bend_contact', 'c_thigh_bend_01', 'c_thigh_bend_02', 'c_knee_bend', 'c_leg_bend_01', 'c_leg_bend_02', 'c_ankle_bend']

                for bn in secondary_list:
                    ebn = get_edit_bone(bn+side)
                    if ebn:
                        delete_edit_bone(ebn)

                    # proxy
                for bn in secondary_list:
                    ebn_proxy = get_edit_bone(bn+'_proxy'+side)
                    if ebn_proxy:
                        delete_edit_bone(ebn_proxy)

                    # remove any additional twist controllers
                for bname in twist_bones_list:
                    b_twist = get_edit_bone(bname)
                    c_twist_name = 'c_' + bname
                    c_twist = get_edit_bone(c_twist_name)
                    # remove
                    if c_twist:
                        delete_edit_bone(c_twist)
                    # enable base twist bone deform
                    b_twist.use_deform = True

                remove_twist_based_constraints(side)
                remove_twist_based_segments(side)
                remove_twist_based_bendy(side)
                remove_twist_based_str(side)

                bpy.ops.object.mode_set(mode='POSE')

    
    rig.data.pose_position = 'POSE'

    if scn.arp_debug_mode == True:
        print("\n FINISH ALIGNING LEG BONES...\n")

    # --end _align_leg_limbs()


def set_3_bones_ik_chain(pole_angles_dict, side):
    print("  set 3 bones IK chain..")
    # setup the 3 bones IK chain
    thigh_b_ik01_name = "thigh_b_ik01" + side
    thigh_b_ik01 = get_edit_bone(thigh_b_ik01_name)
    c_thigh_b = get_edit_bone("c_thigh_b" + side)
    thigh_b_ref = get_edit_bone("thigh_b_ref" + side)

    # bone1
    if thigh_b_ik01 == None:
        thigh_b_ik01 = bpy.context.active_object.data.edit_bones.new(thigh_b_ik01_name)
    thigh_b_ik01.use_deform = False
    thigh_b_ik01.parent = c_thigh_b.parent
    c_thigh_b.parent = thigh_b_ik01
    set_bone_layer(thigh_b_ik01, 8)

    copy_bone_transforms(thigh_b_ref, thigh_b_ik01)

    # bone2
    thigh_b_ik02_name = "thigh_b_ik02" + side
    thigh_b_ik02 = get_edit_bone(thigh_b_ik02_name)
    if thigh_b_ik02 == None:
        thigh_b_ik02 = bpy.context.active_object.data.edit_bones.new(thigh_b_ik02_name)
    thigh_b_ik02.use_deform = False
    set_bone_layer(thigh_b_ik02, 8)
    thigh_b_ik02.parent = thigh_b_ik01
    copy_bone_transforms(get_edit_bone("thigh" + side), thigh_b_ik02)

    # bone3
    thigh_b_ik03_name = "thigh_b_ik03" + side
    thigh_b_ik03 = get_edit_bone(thigh_b_ik03_name)
    if thigh_b_ik03 == None:
        thigh_b_ik03 = bpy.context.active_object.data.edit_bones.new(thigh_b_ik03_name)
    thigh_b_ik03.use_deform = False
    thigh_b_ik03.parent = thigh_b_ik02
    set_bone_layer(thigh_b_ik03, 8)

    copy_bone_transforms(get_edit_bone("leg" + side), thigh_b_ik03)

    # get the pole angle
    pole_angle = get_pole_angle(thigh_b_ik01, thigh_b_ik03, get_edit_bone("c_leg_pole" + side).head)
    pole_angles_dict[side] = pole_angle


def unset_3_bones_ik_chain(side):
    if bpy.context.scene.arp_debug_mode:
        print("  unset 3 bones IK chain..")

    # setup the 3 bones IK chain
    thigh_b_ik01_name = "thigh_b_ik01" + side
    c_thigh_b = get_edit_bone("c_thigh_b" + side)
    thigh_b_ref = get_edit_bone("thigh_b_ref" + side)

    # bone1
    thigh_b_ik01 = get_edit_bone(thigh_b_ik01_name)
    if thigh_b_ik01:
        c_thigh_b.parent = thigh_b_ik01.parent
        delete_edit_bone(thigh_b_ik01)

        # bone2
    thigh_b_ik02_name = "thigh_b_ik02" + side
    thigh_b_ik02 = get_edit_bone(thigh_b_ik02_name)
    if thigh_b_ik02:
        delete_edit_bone(thigh_b_ik02)

        # bone3
    thigh_b_ik03_name = "thigh_b_ik03" + side
    thigh_b_ik03 = get_edit_bone(thigh_b_ik03_name)
    if thigh_b_ik03:
        delete_edit_bone(thigh_b_ik03)


def unset_3_bones_ik_constraint(side):
    # remove copy rot constraint
    #print(" remove copy rot...")
    c_thigh_b = get_pose_bone("c_thigh_b" + side)
    rot_cns = c_thigh_b.constraints.get("Copy Rotation")
    if rot_cns:
        c_thigh_b.constraints.remove(rot_cns)

    # remove property
    foot_ik_name = "c_foot_ik" + side
    c_foot_ik = get_pose_bone(foot_ik_name)

    if len(c_foot_ik.keys()) > 0:
        if "three_bones_ik" in c_foot_ik.keys():
            del c_foot_ik["three_bones_ik"]


def set_3_bones_constraint(pole_angles_dict, side):
    # add 3 bones IK constraint
    print("  set 3 ik bones constraints...")
    thigh_b_ik03_name = "thigh_b_ik03" + side
    thigh_b_ik03 = get_pose_bone(thigh_b_ik03_name)
    ik_cns = thigh_b_ik03.constraints.get("IK")
    if ik_cns == None:
        ik_cns = thigh_b_ik03.constraints.new("IK")
    ik_cns.name = "IK"
    ik_cns.target = bpy.context.active_object
    ik_cns.subtarget = "foot_ik_target" + side
    ik_cns.pole_target = bpy.context.active_object
    ik_cns.pole_subtarget = "c_leg_pole" + side
    ik_cns.pole_angle = pole_angles_dict[side]
    ik_cns.use_tail = True
    ik_cns.chain_count = 3

    # set influence driver
    foot_ik_name = "c_foot_ik" + side
    c_foot_ik = get_pose_bone(foot_ik_name)

    prop_found = False
    if len(c_foot_ik.keys()):
        if not "three_bones_ik" in c_foot_ik.keys():
            create_custom_prop(node=c_foot_ik, prop_name='three_bones_ik', prop_val=0.0, prop_min=0.0, prop_max=1.0, prop_description="Use full 3 bones IK chain")          

    #   set driver
    thigh_b_ik03_name = "thigh_b_ik03" + side
    dp_3_ik = 'pose.bones["' + thigh_b_ik03_name + '"].constraints["IK"].influence'
    dr = bpy.context.active_object.animation_data.drivers.find(dp_3_ik)
    if dr == None:
        dr = bpy.context.active_object.driver_add(dp_3_ik, -1)
    dr.driver.expression = 'inf * (1-switch)'
    inf_var = dr.driver.variables.get("inf")
    if inf_var == None:
        inf_var = dr.driver.variables.new()
        inf_var.name = "inf"
        inf_var.type = "SINGLE_PROP"
        inf_var.targets[0].id = bpy.context.active_object
        inf_var.targets[0].data_path = 'pose.bones["' + foot_ik_name + '"]["three_bones_ik"]'

    switch_var = dr.driver.variables.get("switch")
    if switch_var == None:
        switch_var = dr.driver.variables.new()
        switch_var.name = "switch"
        switch_var.type = "SINGLE_PROP"
        switch_var.targets[0].id = bpy.context.active_object
        switch_var.targets[0].data_path = 'pose.bones["' + foot_ik_name + '"]["ik_fk_switch"]'


def init_arp_scale(rig_name, rig_add=None):
    if rig_add:
        unhide_object(rig_add)
        rig_add.scale = bpy.data.objects[rig_name].scale
        bpy.ops.object.mode_set(mode='OBJECT')
        bpy.ops.object.select_all(action='DESELECT')
        set_active_object(rig_add.name)
        bpy.ops.object.mode_set(mode='OBJECT')

        bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)

    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.select_all(action='DESELECT')
    set_active_object(rig_name)
    bpy.ops.object.mode_set(mode='OBJECT')

    # first unparent children meshes (init scale messed up children scale in Blender 2.8)
    child_par_dict = {}
    for child in bpy.data.objects[rig_name].children:
        bone_parent = None
        if child.parent_type == "BONE":
            bone_parent = child.parent_bone
        child_par_dict[child.name] = bone_parent
        child_mat = child.matrix_world.copy()
        child.parent = None
        bpy.context.evaluated_depsgraph_get().update()
        child.matrix_world = child_mat

    # apply armature scale
    bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
    bpy.context.evaluated_depsgraph_get().update()

    # restore armature children
    for child_name in child_par_dict:
        child = bpy.data.objects.get(child_name)
        child_mat = child.matrix_world.copy()
        child.parent = bpy.data.objects[rig_name]
        if child_par_dict[child_name] != None:# bone parent
            child.parent_type = "BONE"
            child.parent_bone = child_par_dict[child_name]

        bpy.context.evaluated_depsgraph_get().update()
        child.matrix_world = child_mat

    # hide the rig_add
    if rig_add:
        rig_add.select_set(state=False)
        hide_object(rig_add)


def _set_inverse():
    # store the current pose
    bpy.ops.pose.select_all(action='SELECT')
    bpy.ops.pose.copy()
    # reset the pose and child of constraints
    auto_rig_reset.reset_all()
    # restore the pose
    bpy.ops.pose.paste(flipped=False)
    
    
def get_tail_count(side):
    tail_00_ref_name = 'tail_00_ref'+side
    tail_00_ref = get_edit_bone(tail_00_ref_name)
    tail_count = 0
    
    if 'tail_count' in tail_00_ref.keys():
        tail_count = tail_00_ref['tail_count']        
    else:#backward-compatibility
        tail_count = 0
        for i in range(0, 32):
            tail_ref_name = "tail_" + '%02d' % i + '_ref' + side
            tail_ref = get_edit_bone(tail_ref_name)
            if tail_ref:
                tail_count = i+1
                
    return tail_count
    
    
def align_tail_limbs(tside):
    # main tail bones
    last_existing_tail = None
    tail_parent = None
    tail_count = 0
    
    tail_00_ref_name = 'tail_00_ref'+tside
    tail_00_ref = get_edit_bone(tail_00_ref_name)  
    tail_count = get_tail_count(tside)
    
    for i in range(0, tail_count):
        bone_name = 'tail_' + '%02d' % i
        c_bone = get_edit_bone("c_" + bone_name + tside)
        ref_bone = get_edit_bone(bone_name + "_ref" + tside)

        if c_bone and ref_bone:
            copy_bone_transforms(ref_bone, c_bone)
            last_existing_tail = ref_bone.tail.copy()
            
            # parent
            if 'tail_00' in bone_name:
                b_parent = None

                if ref_bone.parent: 
                    p_side = get_bone_side(ref_bone.parent.name)                        
                    retarget_parent_name = 'c_' + ref_bone.parent.name.replace('_ref'+p_side, p_side)
                    b_parent = get_edit_bone(retarget_parent_name)
                    
                    if b_parent == None:# the parent is not ref bone, or the associated controller can't be found easily
                        b_parent = ref_bone.parent
                        
                traj_parent = get_edit_bone(get_first_master_controller())

                if b_parent:
                    c_bone.parent = b_parent
                    tail_parent = b_parent
                    
                elif traj_parent:
                    c_bone.parent = traj_parent

        else:
            print("Ref or control tail bone not found:", bone_name)

            
    # master tail bone
    c_tail_master_name = 'c_tail_master'+tside
    c_tail_master = get_edit_bone(c_tail_master_name)

    if c_tail_master:
        tail_00_ref = get_edit_bone(tail_00_ref_name)
        master_at_root = False
        if "master_at_root" in tail_00_ref.keys():
            master_at_root = tail_00_ref["master_at_root"]

        tail_vec = tail_00_ref.tail - tail_00_ref.head
        if last_existing_tail:
            tail_vec = last_existing_tail - tail_00_ref.head

        tail_origin = tail_00_ref.head.copy()
      
        if not master_at_root:
            c_tail_master.head = tail_origin + (tail_vec * 0.5)
            c_tail_master.tail = c_tail_master.head + (tail_vec * 0.5)
        else:
            c_tail_master.head = tail_origin
            c_tail_master.tail = c_tail_master.head + (tail_00_ref.tail - tail_00_ref.head) * 2.0
            
        c_tail_master.roll = get_edit_bone("tail_00_ref"+tside).roll
        c_tail_master.parent = tail_parent
        c_tail_master.use_deform = False


def _align_spine_limbs():
    print("\n Aligning spine bones...\n")

    # Unit scale
    unit_scale = 1.0
    scn = bpy.context.scene

    if scn.unit_settings.system != 'NONE':
        unit_scale = 1 / scn.unit_settings.scale_length

    rig = get_object(bpy.context.active_object.name)
    rig_add = get_rig_add(rig)

    # Get reference bones
    root_ref_name = ard.spine_ref_dict['root']#"root_ref.x"
    c_root_name = ard.spine_bones_dict['c_root']# c_root.x
    root_name = ard.spine_bones_dict['root']# c_root.x
    c_p_root_name = ard.spine_bones_dict['root_shape_override']# c_root.x
    c_root_master_name = ard.spine_bones_dict['c_root_master']
    root_master_shape_over_name = ard.spine_bones_dict['root_master_shape_override']# c_p_root_master.x
    c_root_bend_name = ard.spine_bones_dict['c_root_bend']# c_root_bend.x
    spine_01_ref_name = ard.spine_ref_dict['spine_01']
    c_spine_01_name = ard.spine_bones_dict['c_spine_01']
    c_spine_01_bend_name = ard.spine_bones_dict['c_spine_01_bend']
    c_p_spine_01_name = ard.spine_bones_dict['spine_01_shape_override']
    spine_01_name = ard.spine_bones_dict['spine_01']
    c_spine_02_name = ard.spine_bones_dict['c_spine_02']
    c_spine_02_bend_name = ard.spine_bones_dict['c_spine_02_bend']
    c_p_spine_02_name = ard.spine_bones_dict['spine_02_shape_override']
    spine_02_name = ard.spine_bones_dict['spine_02']
    spine_02_ref_name = ard.spine_ref_dict['spine_02']
    c_waist_bend_name = ard.spine_bones_dict['c_waist_bend']
    
    c_bot_name = ard.bot_dict['c_bot']
    bot_ref_name = ard.bot_ref_dict['bot']#"bot_bend_ref"
    
    neck_ref_name = ard.neck_ref_dict['neck']
    neck_name = ard.neck_bones_dict['deform']
    c_neck_name = ard.neck_bones_dict['control']
    c_neck_01_name = ard.neck_bones_dict['control_01']
    c_p_neck_name = ard.neck_bones_dict['c_p']
    c_p_neck_01_name = ard.neck_bones_dict['c_p_01']
    neck_twist_name = ard.neck_bones_dict['twist']
    
    head_ref_name = ard.head_ref[0][:-2]
    c_head_name = ard.heads_dict['control'][:-2]
    c_p_head_name = ard.heads_dict['shape_override'][:-2]
    head_name = ard.heads_dict['deform'][:-2]
    head_scale_fix_name = ard.heads_dict['scale_fix'][:-2]
    
    c_eye_offset_name = ard.eye_bones_dict['eye_offset']['name']   
    jaw_ref_name = ard.mouth_bones_ref_dict['jaw'][:-2]
    c_jaw_name = ard.mouth_bones_dict['c_jawbone']['name'][:-2]
    jaw_name = ard.mouth_bones_dict['jawbone']['name'][:-2]
    jaw_ret_name = ard.mouth_bones_dict['jaw_ret_bone']['name'][:-2]
    
    c_lips_top_mid_name = ard.mouth_bones_dict['c_lips_top_mid']['name']
    c_lips_bot_mid_name = ard.mouth_bones_dict['c_lips_bot_mid']['name']
    c_lips_top_name = ard.mouth_bones_dict['c_lips_top']['name']
    c_lips_top_01_name = ard.mouth_bones_dict['c_lips_top_01']['name']
    c_lips_bot_name = ard.mouth_bones_dict['c_lips_bot']['name']
    c_lips_bot_01_name = ard.mouth_bones_dict['c_lips_bot_01']['name']
    c_lips_smile_name = ard.mouth_bones_dict['c_lips_smile']['name']
    c_lips_corner_mini_name = ard.mouth_bones_dict['c_lips_corner_mini']['name']
    c_lips_roll_top_name = ard.mouth_bones_dict['c_lips_roll_top']['name']
    c_lips_roll_bot_name = ard.mouth_bones_dict['c_lips_roll_bot']['name']
    
    c_tongue_01_name = ard.tongue_bones_dict['c_tong_01']['name'][:-2]
    c_tongue_02_name = ard.tongue_bones_dict['c_tong_02']['name'][:-2]
    c_tongue_03_name = ard.tongue_bones_dict['c_tong_03']['name'][:-2]
    
    c_teeth_top_name = ard.teeth_bones_dict['c_teeth_top_mid']['name'][:-2]
    c_teeth_bot_name = ard.teeth_bones_dict['c_teeth_bot_mid']['name'][:-2]
    c_teeth_top_master_name = ard.teeth_bones_dict['teeth_top_master']['name'][:-2]
    c_teeth_bot_master_name = ard.teeth_bones_dict['teeth_bot_master']['name'][:-2]
    teeth_top_ref_name = ard.teeth_bones_ref_dict['teeth_top']
    teeth_bot_ref_name = ard.teeth_bones_ref_dict['teeth_bot']
    
    bpy.ops.object.mode_set(mode='EDIT')

    # Align root master
    c_root_master = get_edit_bone(c_root_master_name)
    if c_root_master:
        init_selection(c_root_master_name)
        c_root_ref = get_edit_bone(root_ref_name)
        p_root_master = get_edit_bone(root_master_shape_over_name)

        align_root_master = True
        if len(c_root_ref.keys()):
            if "align_root_master" in c_root_ref.keys():# backward-compatibility
                align_root_master = c_root_ref["align_root_master"]
        if align_root_master:
            copy_bone_transforms(c_root_ref, c_root_master)

        # set the visual shape position
        dir = c_root_ref.tail - c_root_ref.head
        p_root_master.head = c_root_master.head.copy()
        p_root_master.tail = p_root_master.head + dir / 1.5

        # set the bone vertical if not quadruped
        if not bpy.context.active_object.arp_rig_type == 'quadruped' and not p_root_master.head[2] == p_root_master.tail[2]:
            p_root_master.tail[1] = p_root_master.head[1]

        align_bone_z_axis(p_root_master, c_root_ref.z_axis)

        # Align root
        init_selection(c_root_name)
        c_root = get_edit_bone(c_root_name)
        root = get_edit_bone(root_name)
        root_ref = get_edit_bone(root_ref_name)
        p_root = get_edit_bone(c_p_root_name)

        c_root.head = root_ref.tail.copy()
        c_root.tail = root_ref.head.copy()
        align_bone_z_axis(c_root, root_ref.z_axis)
        c_root.roll += radians(180)
        copy_bone_transforms(c_root, root)

        # set the visual shape position
        dir = root_ref.tail - root_ref.head
        p_root.head = root_ref.head + (root_ref.tail - root_ref.head) / 2
        p_root.tail = p_root.head + dir

        # set the bone vertical if not quadruped
        if not bpy.context.active_object.arp_rig_type == 'quadruped' and not p_root.head[2] == p_root.tail[2]:
            p_root.tail[1] = p_root.head[1]

        align_bone_z_axis(p_root, root_ref.z_axis)


        # Align root bend
        root_bend = get_edit_bone(c_root_bend_name)
        dir = root_bend.tail - root_bend.head
        root_bend.head = c_root.head + (c_root.tail - c_root.head) / 2
        root_bend.tail = root_bend.head + dir
        if scn.arp_retro_spine_bend:#backward-compatibility
            root_bend.roll = 0
        else:
            align_bone_x_axis(root_bend, root_ref.x_axis)


        # hide the c_root_bend in layer 8 if no secondary controllers
        if rig.arp_secondary_type == "NONE":           
            set_bone_layer(root_bend, 8)
        else:
            set_bone_layer(root_bend, 1)

        bpy.ops.object.mode_set(mode='POSE')

        get_data_bone(c_root_bend_name).hide = False# backward-compatibility fix
        c_root_master_pb = get_pose_bone(c_root_master_name)

        # c_root_master shape
        if align_root_master:
            c_root_master_pb.custom_shape_transform = get_pose_bone(root_master_shape_over_name)
        else:
           c_root_master_pb.custom_shape_transform = None

        bpy.ops.object.mode_set(mode='EDIT')

    # Align bot bend
    for side in ['.l', '.r']:
        bot_ref = get_edit_bone(bot_ref_name + side)
        c_bot_bend = get_edit_bone(c_bot_name + side)

        if bot_ref:
            dir = bot_ref.tail - bot_ref.head
            c_bot_bend.head = bot_ref.head
            c_bot_bend.tail = bot_ref.tail - dir / 2
            if not scn.arp_retro_spine_bend:#backward-compatibility
                c_bot_bend.roll = bot_ref.roll

    
    # Align tails
    for tside in limb_sides.tail_sides:
        align_tail_limbs(tside)

    # Align spine 01
    c_spine_01 = get_edit_bone(c_spine_01_name)
    
    if c_spine_01:
        init_selection(c_spine_01_name)        
        spine_01 = get_edit_bone(spine_01_name)
        spine_01_ref = get_edit_bone(spine_01_ref_name)
        p_spine_01 = get_edit_bone(c_p_spine_01_name)

        copy_bone_transforms(spine_01_ref, c_spine_01)
        copy_bone_transforms(c_spine_01, spine_01)

        # set the visual shape position
        if p_spine_01:
            p_spine_01.head = c_spine_01.head
            p_spine_01.tail = p_spine_01.head + (c_spine_01.tail - c_spine_01.head)
            p_spine_01.roll = c_spine_01.roll
            # Set the bone vertical if not quadruped
            if not rig.arp_rig_type == 'quadruped' and not p_spine_01.head[2] == p_spine_01.tail[2]:
                p_spine_01.tail[1] = p_spine_01.head[1]
            p_spine_01.parent = c_spine_01

    # Waist bend   
    waist_bend = get_edit_bone(c_waist_bend_name)
    root_ref = get_edit_bone(root_ref_name)

    disable_waist = False
    if rig.arp_secondary_type == "NONE" or rig.arp_secondary_type == "TWIST_BASED":
        disable_waist = True

    if disable_waist:# no secondary controllers or Twist, remove waist controller
        if waist_bend:
            delete_edit_bone(waist_bend)

    else:
        if root_ref:
            created_waist = False

            if waist_bend == None:
                waist_bend = create_edit_bone(c_waist_bend_name)
                set_bone_layer(waist_bend, 1)
                # set parent
                waist_bend.parent = get_edit_bone(c_root_name)
                created_waist = True

            # align
            waist_bend.head = root_ref.tail.copy()
            waist_bend.tail = root_ref.tail + (root_ref.tail - root_ref.head) * 0.5

            if scn.arp_retro_spine_bend:# backward-compatibility
                waist_bend.roll = 0
            else:
                align_bone_x_axis(waist_bend, root_ref.x_axis)

            # set deform
            if rig.arp_secondary_type == "ADDITIVE":
                waist_bend.use_deform = False
            else:
                waist_bend.use_deform = True

            if created_waist:
                bpy.ops.object.mode_set(mode='POSE')

                waist_pb = get_pose_bone(c_waist_bend_name)
                # set rot mode
                waist_pb.rotation_mode = "XYZ"
                # set group
                waist_pb.bone_group = get_pose_bone(c_root_master_name).bone_group
                # set custom shape
                set_bone_custom_shape(waist_pb, "cs_torus_01")

                bpy.ops.object.mode_set(mode='EDIT')


    # Spine_01_bend    
    spine_01_bend = get_edit_bone(c_spine_01_bend_name)
    c_spine_01 = get_edit_bone(c_spine_01_name)

    if c_spine_01:
        if rig.arp_secondary_type == "NONE":
            if spine_01_bend:
                delete_edit_bone(spine_01_bend)

        else:
            created_bone = False

            if spine_01_bend == None:
                spine_01_bend = create_edit_bone(c_spine_01_bend_name)
                # set parent
                spine_01_bend.parent = get_edit_bone(spine_01_name)
                # set layer
                set_bone_layer(spine_01_bend, 1)
                created_bone = True

            # align
            if scn.arp_retro_spine_bend:#backward-compatibility
                spine_01_bend.head = (c_spine_01.tail + c_spine_01.head) * 0.5
                spine_01_bend.tail = c_spine_01.head.copy()
                spine_01_bend.roll = 0
            else:
                copy_bone_transforms(c_spine_01, spine_01_bend)
                spine_01_bend.tail = spine_01_bend.head + (spine_01_bend.tail-spine_01_bend.head)*0.75

            # backward-compatibility
            if not created_bone:
                spine_01_bend.hide = False
                get_data_bone(c_spine_01_bend_name).hide = False
                spine_01_bend.use_deform = True

            if created_bone:
                bpy.ops.object.mode_set(mode='POSE')

                spine_01_bend_pb = get_pose_bone(c_spine_01_bend_name)
                # set rot mode
                spine_01_bend_pb.rotation_mode = "XYZ"
                # set group
                spine_01_bend_pb.bone_group = get_pose_bone(c_root_master_name).bone_group
                # set custom shape
                set_bone_custom_shape(spine_01_bend_pb, "cs_torus_01")

                bpy.ops.object.mode_set(mode='EDIT')


    # Align spine 02   
    c_spine_02 = get_edit_bone(c_spine_02_name)

    if c_spine_02:
        init_selection(c_spine_02_name)

        spine_02 = get_edit_bone(spine_02_name)
        spine_02_ref = get_edit_bone(spine_02_ref_name)
        p_spine_02 = get_edit_bone(c_p_spine_02_name)

        copy_bone_transforms(spine_02_ref, c_spine_02)
        copy_bone_transforms(c_spine_02, spine_02)

        # set the visual shape position
        if p_spine_02:
            p_spine_02.head = c_spine_02.head
            p_spine_02.tail = p_spine_02.head + (c_spine_02.tail - c_spine_02.head) * 0.5
            p_spine_02.roll = c_spine_02.roll

            # set the bone vertical if not quadruped
            if not rig.arp_rig_type == 'quadruped' and not p_spine_02.head[2] == p_spine_02.tail[2]:
                p_spine_02.tail[1] = p_spine_02.head[1]
                
            p_spine_02.parent = c_spine_02

        # Align spine_02_bend        
        spine_02_bend = get_edit_bone(c_spine_02_bend_name)

        if rig.arp_secondary_type == "NONE":
            if spine_02_bend:
                delete_edit_bone(spine_02_bend)

        else:
            created_bone = False

            if spine_02_bend == None:
                spine_02_bend = rig.data.edit_bones.new(c_spine_02_bend_name)
                # set parent
                spine_02_bend.parent = spine_02
                # set layer
                set_bone_layer(spine_02_bend, 1)
                created_bone = True

            # backward-compatibility
            if not created_bone:
                spine_02_bend.hide = False
                get_data_bone(c_spine_02_bend_name).hide = False
                spine_02_bend.use_deform = True

            # align
            if scn.arp_retro_spine_bend:#backward-compatibility
                spine_02_bend.head = ((c_spine_02.tail + c_spine_02.head) * 0.5)
                spine_02_bend.tail = c_spine_02.head.copy()
                spine_02_bend.roll = 0
            else:
                copy_bone_transforms(c_spine_02, spine_02_bend)
                spine_02_bend.tail = spine_02_bend.head + (spine_02_bend.tail-spine_02_bend.head)*0.75

            if created_bone:
                bpy.ops.object.mode_set(mode='POSE')

                spine_02_bend_pb = get_pose_bone(c_spine_02_bend_name)
                # set rot mode
                spine_02_bend_pb.rotation_mode = "XYZ"
                # set group
                spine_02_bend_pb.bone_group = get_pose_bone(c_root_master_name).bone_group
                # set custom shape
                set_bone_custom_shape(spine_02_bend_pb, "cs_torus_01")

                bpy.ops.object.mode_set(mode='EDIT')


    # Align spine_03 and higher
    for idx in range(3, rig.rig_spine_count+1):
        str_idx = '%02d' % idx
        spine_ref = get_edit_bone(ard.get_spine_name('ref', idx))#get_edit_bone('spine_'+str_idx+'_ref.x')
        c_spine = get_edit_bone(ard.get_spine_name('control', idx))#get_edit_bone('c_spine_'+str_idx+'.x')
        spine = get_edit_bone(ard.get_spine_name('base', idx))#get_edit_bone('spine_'+str_idx+'.x')

        if spine_ref and c_spine and spine:
            copy_bone_transforms(spine_ref, c_spine)
            copy_bone_transforms(spine_ref, spine)

            # Align Spine_bend
            spine_bend_name = ard.get_spine_name('control_bend', idx)#"c_spine_"+str_idx+"_bend.x"
            spine_bend = get_edit_bone(spine_bend_name)

            if rig.arp_secondary_type == "NONE":
                if spine_bend:
                    delete_edit_bone(spine_bend)

            else:
                created_bone = False

                if spine_bend == None:
                    spine_bend = create_edit_bone(spine_bend_name)
                    # set parent
                    spine_bend.parent = c_spine
                    # set layer
                    set_bone_layer(spine_bend, 1)
                    created_bone = True

                spine_bend.use_deform = True
                spine_bend.hide = False

                # align
                if scn.arp_retro_spine_bend:#backward-compatibility
                    spine_bend.head = ((c_spine.tail + c_spine.head) * 0.5)
                    spine_bend.tail = c_spine.head.copy()
                    spine_bend.roll = 0
                else:
                    copy_bone_transforms(c_spine, spine_bend)
                    spine_bend.tail = spine_bend.head + (spine_bend.tail-spine_bend.head)*0.75

                if created_bone:
                    bpy.ops.object.mode_set(mode='POSE')

                    get_data_bone(spine_bend_name).hide = False# backward-compatibility fix

                    spine_bend_pb = get_pose_bone(spine_bend_name)
                    # set rot mode
                    spine_bend_pb.rotation_mode = "XYZ"
                    # set group
                    spine_bend_pb.bone_group = get_pose_bone(c_root_master_name).bone_group
                    # set custom shape
                    set_bone_custom_shape(spine_bend_pb, "cs_torus_01")
                    
                    bpy.ops.object.mode_set(mode='EDIT')

    # Align spine master bones
    align_spine_master_bones(rig)       

    print("\n Aligning heads")
    for dupli in limb_sides.head_sides:
        print('\n [' + dupli + ']')

        # Neck
        c_neck_name = "c_neck"+dupli
        c_neck = get_edit_bone(c_neck_name)
        
        if c_neck:
            init_selection(c_neck_name)            
            neck = get_edit_bone(neck_name[:-2]+dupli)
            p_neck = get_edit_bone(c_p_neck_name[:-2] + dupli)
            p_neck_01 = get_edit_bone(c_p_neck_01_name[:-2] + dupli)
            neck_ref = get_edit_bone(neck_ref_name[:-2] + dupli)
            c_neck_01 = get_edit_bone(c_neck_01_name[:-2] + dupli)

            # The c_neck_01 controller is only needed when secondary controllers are not None
            if rig.arp_secondary_type == "NONE":
                if c_neck_01:
                    delete_edit_bone(c_neck_01)
                    c_neck_01 = get_edit_bone(c_neck_01_name[:-2] + dupli)# update the var to None
            else:
                if c_neck_01 == None:
                    c_neck_01 = create_edit_bone(c_neck_01_name[:-2] + dupli)
                    c_neck_01.head, c_neck_01.tail = [0, 0, 0], [0, 0, 1]
                    set_bone_layer(c_neck_01, 1)

            # neck parent
            if neck_ref.parent:
                print(" Set neck parent...")
                c_neck.parent = parent_retarget(neck_ref)
                if c_neck_01:
                    c_neck_01.parent = c_neck.parent
            else:
                print(" No neck ref parent")
                traj_parent = get_edit_bone(get_first_master_controller())
                if traj_parent:
                    c_neck.parent = traj_parent
                    print(" ...assigning to:", get_first_master_controller())
                    if c_neck_01:
                        c_neck_01.parent = traj_parent

            # neck coordinates
            copy_bone_transforms(neck_ref, c_neck)
            copy_bone_transforms(neck_ref, neck)

            # neck_twist_target coordinates
            neck_twist_tar_name = ard.neck_bones_dict['twist_target'][:-2]+dupli
            neck_twist_tar = get_edit_bone(neck_twist_tar_name)
            if neck_twist_tar:
                head_ref = get_edit_bone(head_ref_name+dupli)
                copy_bone_transforms(neck_ref, neck_twist_tar)
                move_bone_to_bone(neck_twist_tar, head_ref)
                neck_twist_tar.tail = neck_twist_tar.head + (neck_twist_tar.tail-neck_twist_tar.head)*0.5

            # neck_01 coordinates
            if c_neck_01:
                c_neck_01.head = neck_ref.head
                c_neck_01.tail = c_neck_01.head
                c_neck_01.tail[1] += -neck_ref.length / 3
                c_neck_01.roll = 0

            # set the visual shape position
            copy_bone_transforms(neck_ref, p_neck)
            p_neck.head += (neck_ref.tail - neck_ref.head) / 2
            p_neck.tail = p_neck.head + (neck_ref.tail - neck_ref.head)

            p_neck_01.head = neck_ref.head
            p_neck_01.head[1] += -0.07
            p_neck_01.tail = p_neck_01.head
            p_neck_01.tail[1] += -0.03
            

        # Head
        head_ref = get_edit_bone(head_ref_name + dupli)
        lips_roll_cns = False
        lips_roll_speed = 1.0
        
        if head_ref:
            if 'lips_roll_cns' in head_ref.keys():#backward-compatibility
                lips_roll_cns = head_ref['lips_roll_cns']
            if 'lips_roll_speed' in head_ref.keys():#backward-compatibility
                lips_roll_speed = head_ref['lips_roll_speed']
                
            init_selection(c_head_name + dupli)
            c_head = get_edit_bone(c_head_name + dupli)            
            head = get_edit_bone(head_name + dupli)
            head_scale_fix = get_edit_bone(head_scale_fix_name + dupli)
            c_p_head = get_edit_bone(c_p_head_name + dupli)
            neck_twist = get_edit_bone(neck_twist_name[:-2] + dupli)

            copy_bone_transforms(head_ref, c_head)
            copy_bone_transforms(head_ref, head)
            copy_bone_transforms(head_ref, head_scale_fix)
            if neck_twist:  # retro-compatibility
                copy_bone_transforms(head_ref, neck_twist)
                neck_twist.tail = neck_twist.head + (neck_twist.tail - neck_twist.head) * 0.5

            # set the visual shape position
            if c_p_head:
                c_p_head.head = head.tail
                c_p_head.tail = c_p_head.head + (head.tail - head.head) / 2
                c_p_head.roll = head.roll

                
            # Skulls
            skulls = [ard.skulls_dict['01'][:-2] + dupli, ard.skulls_dict['02'][:-2] + dupli, ard.skulls_dict['03'][:-2] + dupli]
            jaw_ref = get_edit_bone(jaw_ref_name + dupli)
            project_vec = None
            head_vec = head_ref.tail - head_ref.head

            # if facial is enabled, align skulls with the jaw tail (chin) height for more precise placement. Available only for the main facial, no duplicate
            if is_facial_enabled(rig) and not '_dupli' in dupli:
                head_jaw_vec = jaw_ref.tail - head_ref.tail
                project_vec = project_vector_onto_vector(head_jaw_vec, head_vec)

            # else align skulls at 1/3 of the neck height
            else:
                neck_ref = get_edit_bone(neck_ref_name[:-2]+dupli)
                head_neck_vec = (neck_ref.tail + (neck_ref.head - neck_ref.tail) * 0.3) - head_ref.tail
                project_vec = project_vector_onto_vector(head_neck_vec, head_vec)

            # start aligning skulls
            i = 0
            skulls_align = True
            if "skulls_align" in head_ref.keys():
                skulls_align = head_ref["skulls_align"]

            if skulls_align:
                for skull in skulls:
                    skull_bone = get_edit_bone(skull)                    
                    
                    if skull_bone:
                        # can be custom bone from Quick Rig. Skip it
                        if 'cc' in skull_bone.keys():
                            continue                    
                    
                        if i == 0:
                            skull_bone.head = head_ref.tail + project_vec * 0.67
                            skull_bone.tail = head_ref.tail + project_vec
                            skull_bone.roll = radians(90)
                        if i == 1:
                            skull_bone.head = head_ref.tail + project_vec * 0.67
                            skull_bone.tail = head_ref.tail + project_vec * 0.3333
                            skull_bone.roll = 0
                        if i == 2:
                            skull_bone.head = head_ref.tail + project_vec * 0.3333
                            skull_bone.tail = head_ref.tail.copy()
                            skull_bone.roll = 0

                    i += 1

            if "skull" in locals():
                del skull

        # Align facial
        print('\n Aligning facial...')
        
        # mouth
        c_jaw = get_edit_bone(c_jaw_name + dupli)       
        jaw_ref = get_edit_bone(jaw_ref_name + dupli)
        
        if c_jaw and jaw_ref:
            print("  Mouth...")
            roll_speed_dict = {}
            
            # backward-compatibility
            # old case, the jaw is rotation based
            jaw = get_edit_bone(jaw_name + dupli)
            jaw_speed = 1.0
            if jaw == None:
                copy_bone_transforms(jaw_ref, c_jaw)
            else:
                # new case, the jaw is translation based
                if 'jaw_speed' in head_ref.keys():
                    jaw_speed = head_ref['jaw_speed']
                    
                copy_bone_transforms(jaw_ref, jaw)
                pos_fac = 1.0 if jaw_speed <= 1.0 else 1/jaw_speed
                c_jaw.head = jaw.head + (jaw.tail - jaw.head) * 0.5 * pos_fac
                c_jaw.tail = c_jaw.head + (jaw.tail - jaw.head) * 0.5
                c_jaw.roll = jaw.roll
                
                # update lips retain drivers
                for driver in rig.animation_data.drivers:
                    dp_prop = driver.data_path.split(".")[len(driver.data_path.split(".")) - 1]
                    if jaw_ret_name + dupli in driver.data_path and dp_prop == "scale":
                        jaw_ret_bone_name = driver.data_path.split('"')[1]
                        print("  jaw_ret =", jaw_ret_bone_name)
                        jaw_ret_length = str(round(get_data_bone(jaw_ret_bone_name).length, 4) / jaw_speed)
                        dr = driver.driver
                        dr.expression = 'max(0.05, 1 - (jaw_rot / ' + jaw_ret_length + ') * stretch_value)'
                        
                if "driver" in locals():
                    del driver

            # jaw_retain
            jaw_ret_bone = get_edit_bone(jaw_ret_name + dupli)
            if jaw_ret_bone:
                copy_bone_transforms(jaw_ref, jaw_ret_bone)
                jaw_ret_bone.tail = jaw_ret_bone.head + (jaw_ret_bone.tail - jaw_ret_bone.head) * 0.8

            ###############################
            # jaw base (experimental)
            jaw_base_bone = get_edit_bone("jaw_base" + dupli)
            if jaw_base_bone:
                copy_bone_transforms(jaw_ref, jaw_base_bone)

            # lips_corner_middle (experimental)
            lips_cor_mid_name = "lips_corner_middle" + dupli
            lips_cor_mid = get_edit_bone(lips_cor_mid_name)
            if lips_cor_mid:
                copy_bone_transforms(jaw_ref, lips_cor_mid)
                lips_cor_mid.tail += (lips_cor_mid.head - lips_cor_mid.tail) * 0.2

            # lips_retain_corner (experimental)
            for lat_side in [".l", ".r"]:
                lips_ret_corn_name = "lips_retain_corner" + dupli[:-2] + lat_side
                lips_ret_corn = get_edit_bone(lips_ret_corn_name)
                if lips_ret_corn:
                    copy_bone_transforms(jaw_ref, lips_ret_corn)
                    lips_ret_corn.tail += (lips_ret_corn.head - lips_ret_corn.tail) * 0.4

            # lips masters (experimental)
            lips_top_big_master_ref = get_edit_bone("lips_top_big_master_ref" + dupli)
            c_lips_top_big_master = get_edit_bone("c_lips_top_big_master" + dupli)
            if lips_top_big_master_ref and c_lips_top_big_master:
                copy_bone_transforms(lips_top_big_master_ref, c_lips_top_big_master)

            lips_bot_big_master_ref = get_edit_bone("lips_bot_big_master_ref" + dupli)
            c_lips_bot_big_master = get_edit_bone("c_lips_bot_big_master" + dupli)
            if lips_bot_big_master_ref and c_lips_bot_big_master:
                copy_bone_transforms(lips_bot_big_master_ref, c_lips_bot_big_master)
            ###############################    
            
            
            c_lips_names = [c_lips_top_mid_name, c_lips_bot_mid_name, c_lips_top_name, c_lips_bot_name,
                    c_lips_smile_name, c_lips_corner_mini_name, c_lips_roll_top_name, c_lips_roll_bot_name]   
            
            lips_amount = 2
            if 'lips_amount' in head_ref.keys():
                lips_amount = head_ref['lips_amount']
            
            for i in range(1, lips_amount):
                str_idx = '%02d' % i
                c_lips_names.append('c_lips_top_'+str_idx)
                c_lips_names.append('c_lips_bot_'+str_idx)
                
            for c_lip_name in c_lips_names:
                if c_lip_name[-2:] == '.x':
                    _sides = [dupli]
                else:
                    _sides = [dupli[:-2] + '.l', dupli[:-2] + '.r']

                for _side in _sides:
                    ref_name = c_lip_name[2:].replace('.x', '') + '_ref' + _side
                    ref_bone = get_edit_bone(ref_name)
                    
                    
                    # lips controllers
                    c_name = c_lip_name.replace('.x', '') + _side
                    c_lip = get_edit_bone(c_name)
                    if c_lip and ref_bone:
                        copy_bone_transforms(ref_bone, c_lip)
                        # roll speed dict
                        if 'roll_speed' in ref_bone.keys():
                            roll_speed_dict[c_name] = ref_bone['roll_speed']
                        else:
                            roll_speed_dict[c_name] = 1.0
                    
                    # lips offset bones
                    offset_name = c_lip_name.replace('.x', '') + '_offset' + _side
                    if get_edit_bone(offset_name):  # backward-compatibility
                        offset_bone = get_edit_bone(offset_name)
                        copy_bone_transforms(ref_bone, offset_bone)

                    # lips follow bones
                    follow_name = c_lip_name[2:].replace('.x', '') + '_follow' + _side
                    if get_edit_bone(follow_name):  # backward-compatibility
                        follow_bone = get_edit_bone(follow_name)
                        copy_bone_transforms(ref_bone, follow_bone)

                    # lips retain bones
                    retain_name = c_lip_name.replace('.x', '') + '_retain' + _side
                    if get_edit_bone(retain_name):# backward-compatibility
                        retain_bone = get_edit_bone(retain_name)
                        copy_bone_transforms(ref_bone, retain_bone)
                        
                    # lips masters bones
                    master_name = c_lip_name.replace('.x', '') + '_master' + _side
                    master_eb = get_edit_bone(master_name)
                    if master_eb:                       
                        copy_bone_transforms(ref_bone, master_eb)
                        master_eb.tail = master_eb.head + (master_eb.tail-master_eb.head)*1.2
                
            # lips roll constraints            
            bpy.ops.object.mode_set(mode='POSE')
            
            set_lips_roll_constraints(rig, dupli[:-2], enable=lips_roll_cns, global_speed=lips_roll_speed, speed_dict=roll_speed_dict)
            
            bpy.ops.object.mode_set(mode='EDIT')
            
            # tongues
            print("  Tongues...")
            tongs = [c_tongue_01_name + dupli, c_tongue_02_name + dupli, c_tongue_03_name + dupli]
            for tong in tongs:
                current_bone = get_edit_bone(tong)
                bname = tong[2:-2] + "_ref" + dupli
                if "_dupli_" in tong:
                    bname = tong[2:-12] + "_ref" + dupli
                mouth_bone = get_edit_bone(bname)
                if mouth_bone and current_bone:
                    copy_bone_transforms(mouth_bone, current_bone)
                if mouth_bone and get_edit_bone(tong[2:]):
                    copy_bone_transforms(mouth_bone, get_edit_bone(tong[2:]))
            
            # teeths
            print("  Teeth...")
            
            teeth = [c_teeth_top_name+dupli, c_teeth_bot_name+dupli, c_teeth_bot_name+dupli[:-2]+".l",
                     c_teeth_bot_name+dupli[:-2]+".r", c_teeth_top_name+dupli[:-2]+".l",
                     c_teeth_top_name+ dupli[:-2]+".r", c_teeth_top_master_name+dupli, c_teeth_bot_master_name+dupli]                     
            
            for tooth in teeth:
                current_bone = get_edit_bone(tooth)

                if current_bone:
                    if not 'master' in tooth:
                        ref_name = tooth.replace('.', '_ref.')[2:]
                        if "_dupli_" in tooth:
                            ref_name = (tooth[:-12] + "_ref" + dupli)[2:]

                        tooth1 = get_edit_bone(ref_name)
                        if tooth1:
                            copy_bone_transforms(tooth1, current_bone)
                        
                    if tooth == c_teeth_top_master_name+dupli:
                        ref_top_name = teeth_top_ref_name+dupli
                        ref_top = get_edit_bone(ref_top_name)
                        if ref_top:
                            current_bone.head = ref_top.head + (ref_top.head - ref_top.tail) / 2
                            current_bone.tail = ref_top.tail + (ref_top.head - ref_top.tail) / 2
                        
                    if tooth == c_teeth_bot_master_name+dupli:
                        ref_bot_name = teeth_bot_ref_name+dupli
                        ref_bot = get_edit_bone(ref_bot_name)
                        if ref_bot:
                            current_bone.head = ref_bot.head + (ref_bot.head - ref_bot.tail) / 2
                            current_bone.tail = ref_bot.tail + (ref_bot.head - ref_bot.tail) / 2                        
               
            
        # cheeks
        print("  Cheeks...")
        cheeks = ["c_cheek_smile", "c_cheek_inflate"]

        for side in [".l", ".r"]:
            for cheek in cheeks:
                cheek_ref = get_edit_bone(cheek[2:] + "_ref" + dupli[:-2] + side)
                cheek_bone = get_edit_bone(cheek + dupli[:-2] + side)
                copy_bone_transforms(cheek_ref, cheek_bone)

            if "cheek" in locals():
                del cheek

        # nose
        print("  Nose...")
        noses = ["c_nose_01" + dupli, "c_nose_02" + dupli, "c_nose_03" + dupli]
        for nose in noses:
            nose_bone = get_edit_bone(nose)
            ref_name = nose[2:-2] + "_ref" + dupli
            if "_dupli_" in nose:
                ref_name = nose[2:-12] + "_ref" + dupli
            nose_ref = get_edit_bone(ref_name)
            if nose_ref and nose_bone:
                copy_bone_transforms(nose_ref, nose_bone)

        if "nose" in locals():
            del nose

        # chins
        print("  Chins...")
        chins = ["c_chin_01" + dupli, "c_chin_02" + dupli]
        for chin in chins:
            bone = get_edit_bone(chin)
            bname = chin[2:-2] + "_ref" + dupli
            if "_dupli_" in chin:
                bname = chin[2:-12] + "_ref" + dupli
            ref_bone = get_edit_bone(bname)
            if ref_bone and bone:
                copy_bone_transforms(ref_bone, bone)


        # Eyes
        #   main eye bones
        #   make list of all eyes bones
        for eye_side in ['.l', '.r']:
            c_eye_offset_def_name = c_eye_offset_name + dupli[:-2] + eye_side
            c_eye_offset = get_edit_bone(c_eye_offset_def_name)
        
            if c_eye_offset:
                print("  Eyes ", eye_side)
                eyes = []
                init_selection(c_eye_offset_def_name)
            
                bpy.ops.armature.select_similar(type='CHILDREN')
            
                for eb in get_selected_edit_bones()[:]:                    
                    eyes.append(eb.name[:-2])

                # direct copy from ref        
                for eye_name in eyes:
                    # do not align main c_eyelid now, after
                    if eye_name == "c_eyelid_top" or eye_name == "c_eyelid_bot":
                        continue
                    
                    ref_name = eye_name.replace('c_', '') + "_ref" + eye_side
                    cname = eye_name + dupli[:-2] + eye_side
                    if "_dupli_" in eye_name:
                        ref_name = eye_name.replace('c_', '')[:-10] + "_ref" + dupli[:-2] + eye_side
                        cname = eye_name[:-10] + dupli[:-2] + eye_side

                    bone_ref = get_edit_bone(ref_name)
                    current_bone = get_edit_bone(cname)

                    if bone_ref and current_bone:
                        copy_bone_transforms(bone_ref, current_bone)
                    else:
                        if scn.arp_debug_mode:
                            print("Bones don't exist:", ref_name, cname)

                    # eye offset supports custom parent
                    if eye_name == c_eye_offset_def_name[:-2]:
                        print('eye_name', eye_name)
                        if bone_ref.parent:
                            current_bone.parent = bone_ref.parent
                            print("SET EYE OFFSET PARENT", bone_ref.parent.name)
                            
                # Eyelids
                for lvl in ["_top", "_bot"]:
                    bpy.ops.object.mode_set(mode='EDIT')
                    
                    eyelid_name = "eyelid" + lvl + dupli[:-2] + eye_side
                    eyelid_eb = get_edit_bone(eyelid_name)
                    
                    if eyelid_eb:         
                        eyelid_ref_eb = get_edit_bone("eyelid" + lvl + "_ref" + dupli[:-2] + eye_side)
                        copy_bone_transforms(eyelid_ref_eb, eyelid_eb)

                        # if the eyelids bones have constraints, they're up to date: new alignment needed
                        
                        bpy.ops.object.mode_set(mode='POSE')
                        
                        eyelid_pb = get_pose_bone(eyelid_name)

                        if len(eyelid_pb.constraints):
                            if eyelid_pb.constraints[0].type == "TRANSFORM":
                            
                                bpy.ops.object.mode_set(mode='EDIT')
                                
                                c_eyel_name = "c_eyelid" + lvl + dupli[:-2] + eye_side
                                c_eyel = get_edit_bone(c_eyel_name)                                
                                eyelid_eb = get_edit_bone(eyelid_name)                                
                                eye_offset_def_name = "eye_offset_ref" + dupli[:-2] + eye_side
                                eye_offset = get_edit_bone(eye_offset_def_name)
                                
                                c_eyel.head = eyelid_eb.tail + (eyelid_eb.tail - eyelid_eb.head) * 1.5
                                # do not align the eyelid if this setting is disabled
                                align_eyelid_rot = True
                                head_ref = get_edit_bone(head_ref_name + dupli)
                                if "eyelid_align_rot" in head_ref.keys():
                                    align_eyelid_rot = head_ref["eyelid_align_rot"]
                                if align_eyelid_rot:
                                    c_eyel.tail = c_eyel.head + ((eyelid_eb.tail - eyelid_eb.head) * 0.5)
                                    c_eyel.roll = eyelid_eb.roll

                                # set constraint
                                eyelid_speed = 1.0                             
                                if "eyelid_speed_fac" in head_ref.keys():
                                    eyelid_speed = head_ref["eyelid_speed_fac"]
                                    
                                bpy.ops.object.mode_set(mode='POSE')
                                
                                eyelid_pb_name = "eyelid" + lvl + dupli[:-2] + eye_side
                                eyelid_pb = get_pose_bone(eyelid_pb_name)
                                cns = eyelid_pb.constraints[0]
                                cns.from_min_z = 0.0
                                cns.from_max_z = 1.5
                                cns.to_max_x_rot = (1.4 / eyelid_pb.length) * eyelid_speed
                                
                                bpy.ops.object.mode_set(mode='EDIT')
                            else:
                                print("  Old eyelids found, do nothing")
                        else:                            
                            print("  Old eyelids found, do nothing")
                    else:
                        print("  eyelid" + lvl + dupli[:-2] + eye_side, "not found!")

              
        #   additional eye bones
        eye_additions = ["c_eye", "c_eye_ref_track", "c_eyelid_base", "c_eye_ref"]
        
        bpy.ops.object.mode_set(mode='EDIT')
        
        for eye_side in ['.l', '.r']:
            for bname in eye_additions:
                current_bone_name = bname + dupli[:-2] + eye_side
                current_bone = get_edit_bone(current_bone_name)
                eye_offset_ref_name = "eye_offset_ref" + dupli[:-2] + eye_side
                eye_reference = get_edit_bone(eye_offset_ref_name)
                
                if current_bone == None or eye_reference == None:
                    continue
                    
                copy_bone_transforms(eye_reference, current_bone)

                if bname == 'c_eye_ref':
                    current_bone.head = eye_reference.tail + (eye_reference.tail - eye_reference.head)
                    current_bone.tail = current_bone.head
                    current_bone.tail[2] += -0.006
                if bname == 'c_eye_ref_track':
                    current_bone.tail = current_bone.head + (current_bone.tail - current_bone.head) / 2

                    
        eye_target_x_name = "c_eye_target" + dupli
        eye_target_x = get_edit_bone(eye_target_x_name)
        
        if eye_target_x:
            # get the distance between the two eyes for correct shape scale
            eye_l = get_edit_bone("c_eye_target" + dupli[:-2] + ".l")
            eye_r = get_edit_bone("c_eye_target" + dupli[:-2] + ".r")
            eyesballs_dist = 0.1

            # Set the eye target distance according to the head size
            custom_dist = 1.0
            head_ref = get_edit_bone(head_ref_name + dupli)
            if head_ref.get("eye_target_dist"):
                custom_dist = head_ref.get("eye_target_dist")

            dist_from_head = (head_ref.tail - head_ref.head).magnitude * custom_dist

            # Set the eye target scale according to the eyeballs distance
            if eye_l and eye_r:
                eyesballs_dist = (eye_l.head - eye_r.head).magnitude
            elif (eye_l == None and eye_r) or (eye_r == None and eye_l):# cyclope mode             
                eyesballs_dist = (head_ref.tail - head_ref.head).magnitude * 0.5
           
            print("  Eyeball dist:", eyesballs_dist)

            if scn.arp_retro_eyes:
                # old eyes alignment, leads to issues
                for side in [".l", ".r"]:
                    eye_ref = get_edit_bone("eye_offset_ref" + dupli[:-2] + side)
                    # .x
                    eye_target_x.head = eye_ref.head.copy()
                    eye_target_x.head[0] = 0.0
                    eye_target_x.head[1] += -dist_from_head
                    eye_target_x.tail = eye_target_x.head
                    eye_target_x.tail[2] += 0.5 * eyesballs_dist

                    # .l and .r
                    eye_target_side = get_edit_bone("c_eye_target" + dupli[:-2] + side)
                    if round(eye_ref.head[0], 4) == round(eye_ref.tail[0], 4) and round(eye_ref.head[2], 4) == round(eye_ref.tail[2], 4):# if the eye is aligned vert/hor
                        print("\n    Aligned eye:", eye_ref.name)
                        eye_target_side.head = eye_target_x.head
                        eye_target_side.head[0] = eye_ref.head[0]
                        eye_target_side.tail = eye_target_side.head
                        eye_target_side.tail[2] = eye_target_x.tail[2]
                    else:
                        print("\n    Non-aligned eye:", eye_ref.name, round(eye_ref.head[0], 4), round(eye_ref.tail[0], 4), round(eye_ref.head[2], 4), round(eye_ref.tail[2], 4))
                        eye_target_side.head = eye_ref.head + (eye_ref.tail - eye_ref.head) * 10
                        eye_target_side.tail = eye_target_side.head
                        eye_target_side.tail[2] += 0.05

                eye_target_x.head[0] = (eye_l.head[0] + eye_r.head[0]) * 0.5
                eye_target_x.tail[0] = eye_target_x.head[0]

            else:
                # new eyes alignment
                # get the eyes center position and mid vector
                eyes_center = None
                eyes_mid_dir = None
                eye_ref_l = get_edit_bone("eye_offset_ref" + dupli[:-2] + ".l")
                eye_ref_r = get_edit_bone("eye_offset_ref" + dupli[:-2] + ".r")
                
                if eye_ref_l and eye_ref_r:# both eyes are enabled
                    eyes_center = (eye_ref_l.head + eye_ref_r.head) * 0.5
                    eyes_mid_dir = (eye_ref_l.y_axis.normalized() + eye_ref_r.y_axis.normalized()) * 0.5

                    # set c_eye_target.x
                    eye_target_x.head = eyes_center + (eyes_mid_dir * dist_from_head)
                    eye_ref_z_median = (eye_ref_l.z_axis + eye_ref_r.z_axis)*0.5
                    eye_target_x.tail = eye_target_x.head + (eye_ref_z_median.normalized() * 0.5 * eyesballs_dist)
                    align_bone_x_axis(eye_target_x, eyes_mid_dir)
                    
                    # set c_eye_target.l/.r
                    for eye_side in [".l", ".r"]:
                        eye_ref = get_edit_bone("eye_offset_ref" + dupli[:-2] + eye_side)
                        eye_target_side = get_edit_bone("c_eye_target" + dupli[:-2] + eye_side)
                        eye_target_side.head = eye_ref.head + (eye_ref.y_axis.normalized() * dist_from_head)
                        eye_target_side.tail = eye_target_side.head + (eye_ref.z_axis.normalized() * (eye_target_x.tail - eye_target_x.head).magnitude)
                        align_bone_x_axis(eye_target_side, eye_ref.y_axis)
                        if eye_side == ".r":
                            eye_target_side.roll += radians(180)
                            
                elif (eye_ref_l == None and eye_ref_r) or (eye_ref_r == None and eye_ref_l):# cyclope mode
                    eye_ref = eye_ref_l if eye_ref_r == None else eye_ref_r
                    eyes_center = eye_ref.head.copy()
                    eyes_dir = eye_ref.y_axis.normalized()
                    
                    # set c_eye_target.x
                    eye_target_x.head = eyes_center + (eyes_dir * dist_from_head)
                    eye_ref_z = eye_ref.z_axis
                    eye_target_x.tail = eye_target_x.head + (eye_ref_z.normalized() * 0.33 * eyesballs_dist)
                    align_bone_x_axis(eye_target_x, eyes_dir)
                    
                    # set c_eye_target.l/.r 
                    cyclope_side = eye_ref.name[-2:]
                    eye_ref = get_edit_bone("eye_offset_ref" + dupli[:-2] + cyclope_side)
                    eye_target_side = get_edit_bone("c_eye_target" + dupli[:-2] + cyclope_side)
                    eye_target_side.head = eye_ref.head + (eye_ref.y_axis.normalized() * dist_from_head)
                    eye_target_side.tail = eye_target_side.head + (eye_ref.z_axis.normalized() * (eye_target_x.tail - eye_target_x.head).magnitude)
                    align_bone_x_axis(eye_target_side, eye_ref.y_axis)
                    if cyclope_side == ".r":
                        eye_target_side.roll += radians(180)
                    
        
        # eye spec target
        for eye_side in [".l", ".r"]:
            eye_spec_name = 'c_eye_ref_target' + dupli[:-2] + eye_side
            eye_spec = get_edit_bone(eye_spec_name)
            if  eye_spec:        
                eye_target_name = 'c_eye_target' + dupli[:-2] + eye_side
                eye_target = get_edit_bone(eye_target_name)
                if eye_target:
                    eye_spec.head = eye_target.head.copy()
                    eye_spec.tail = eye_target.head + (eye_target.tail - eye_target.head) * 0.75
                    eye_spec.roll = eye_target.roll
        
        # Eyebrows
        for eyeb_side in [".l", ".r"]:
            eyebrows = []
            # make list of eyebrows
            eyeb_full_ref_name = "eyebrow_full_ref" + dupli[:-2] + eyeb_side
            eyeb_full_ref = get_edit_bone(eyeb_full_ref_name)
        
            if eyeb_full_ref:
                init_selection(eyeb_full_ref_name)
            
                bpy.ops.armature.select_similar(type='CHILDREN')
            
                for bone in get_selected_edit_bones()[:]:                  
                    eyebrows.append(bone.name[:-2])
            
                for eyebrow in eyebrows:
                    eyeb_name = "c_" + eyebrow[:-4] + dupli[:-2] + eyeb_side
                    ref_name = eyebrow + dupli[:-2] + eyeb_side
                    if "_dupli" in eyebrow:
                        eyeb_name = "c_" + eyebrow[:-14] + dupli[:-2] + eyeb_side
                        ref_name = eyebrow[:-10] + dupli[:-2] + eyeb_side

                    current_bone = get_edit_bone(eyeb_name)
                    bone_ref = get_edit_bone(ref_name)
                    current_bone.head = bone_ref.head
                    current_bone.tail = bone_ref.tail
                    current_bone.roll = bone_ref.roll

                # Eyebrows Type 2
                #   additional auto rot bones
                eyebrow_01_end = get_edit_bone("eyebrow_01_end" + dupli[:-2] + eyeb_side)
                if eyebrow_01_end:
                    refb = get_edit_bone("eyebrow_01_end_ref" + dupli[:-2] + eyeb_side)
                    eyebrow_01_end.head = refb.head
                    eyebrow_01_end.tail = refb.head + (refb.tail - refb.head) * 0.75
                    eyebrow_01_end.roll = refb.roll

                eyebrow_01_target = get_edit_bone("eyebrow_01_end_target" + dupli[:-2] + eyeb_side)
                if eyebrow_01_target:
                    refb = get_edit_bone("eyebrow_01_end_ref" + dupli[:-2] + eyeb_side)
                    eyeb2 = get_edit_bone("eyebrow_01_ref" + dupli[:-2] + eyeb_side)
                    eyebrow_01_target.head = project_point_onto_plane(refb.head, eyeb2.head, refb.z_axis)
                    eyebrow_01_target.tail = eyebrow_01_target.head + (refb.tail - refb.head)
                    eyebrow_01_target.roll = refb.roll

                # Eyebrows Type 3 (offsets)
                #   master
                c_eyeb_offset_full = get_edit_bone("c_eyebrow_offset_full" + dupli[:-2] + eyeb_side)
                if c_eyeb_offset_full:
                    refb = get_edit_bone("eyebrow_full_ref" + dupli[:-2] + eyeb_side)
                    c_eyeb_offset_full.head, c_eyeb_offset_full.tail, c_eyeb_offset_full.roll = refb.head.copy(), refb.tail.copy(), refb.roll
                    c_eyeb_offset_full.tail = c_eyeb_offset_full.head + (
                            c_eyeb_offset_full.tail - c_eyeb_offset_full.head) * 0.9  # make it slightly smaller to better see it in the viewport

                #   others
                for eyebrow in eyebrows:
                    offset_name = "c_" + eyebrow[:-4] + "_offset" + dupli[:-2] + eyeb_side
                    ref_name = eyebrow + dupli[:-2] + eyeb_side
                    if "_dupli" in eyebrow:
                        offset_name = "c_" + eyebrow[:-14] + "_offset" + dupli[:-2] + eyeb_side
                        ref_name = eyebrow[:-10] + dupli[:-2] + eyeb_side

                    current_bone = get_edit_bone(offset_name)
                    bone_ref = get_edit_bone(ref_name)
                    if current_bone == None or bone_ref == None:
                        # print("  Bones not found:", offset_name, current_bone, ref_name, bone_ref)
                        continue
                    current_bone.head = bone_ref.head
                    current_bone.tail = bone_ref.tail
                    current_bone.tail = current_bone.head + (
                            current_bone.tail - current_bone.head) * 0.9  # make it slightly smaller to better see it in the viewport
                    current_bone.roll = bone_ref.roll
                    

        # Subnecks
        has_subnecks = False
        for i in range(1, 17):
            subneck_ref = get_edit_bone('subneck_'+str(i)+"_ref"+dupli)
            cont_subneck = get_edit_bone('c_subneck_'+str(i)+dupli)
            twist_subneck = get_edit_bone('subneck_twist_'+str(i)+dupli)
            subneck_twist_tar = get_edit_bone('subneck_twist_tar_'+str(i)+dupli)
            head_ref = get_edit_bone('head_ref'+dupli)
            if subneck_ref and cont_subneck:
                has_subnecks = True
                # controller
                copy_bone_transforms(subneck_ref, cont_subneck)
                # twist
                if twist_subneck:
                    copy_bone_transforms(subneck_ref, twist_subneck)
                # twist target
                if subneck_twist_tar:
                    copy_bone_transforms(subneck_ref, subneck_twist_tar)
                    subneck_twist_tar.tail = subneck_twist_tar.head + (subneck_twist_tar.tail-subneck_twist_tar.head)*0.5
                    move_bone_to_bone(subneck_twist_tar, head_ref)
                # parent
                if i == 1 and cont_subneck:
                    _parent = None
                    if subneck_ref.parent:
                        parent_name = ''
                        par_retarget = parent_retarget(subneck_ref)
                        if par_retarget:
                            parent_name = par_retarget.name
                        #parent_name = subneck_ref.parent.name.replace("_ref", "")
                        if get_edit_bone(parent_name):
                            _parent = get_edit_bone(parent_name)
                            print("Found subneck parent", parent_name)
                        else:
                            _parent = subneck_ref.parent
                            print("Assign subneck parent", _parent.name)

                    cont_subneck.parent = _parent

        if "i" in locals():
            del i

        # subneck master
        c_neck_master = get_edit_bone("c_neck_master"+dupli)
        if c_neck_master:
            first_subneck = get_edit_bone('c_subneck_1'+dupli)
            neck_ref = get_edit_bone("neck_ref"+dupli)
            neck_origin = first_subneck.head.copy()
            neck_vec =  neck_ref.tail - first_subneck.head
            align_neck_master(_neck_master=c_neck_master, _origin=neck_origin, _neck_vec=neck_vec, _neck_ref=neck_ref, _parent=first_subneck.parent)

            
    print("\n Aligning ears")
    for dupli in limb_sides.ear_sides:

        print('[' + dupli + ']')

        ears_list = []

        for ear_id in range(0, 17):
            ear_n = 'ear_' + '%02d' % ear_id + '_ref' + dupli
            if get_edit_bone(ear_n):
                ears_list.append('ear_' + '%02d' % ear_id)

        if "ear_id" in locals():
            del ear_id

        for ear in ears_list:
            if get_edit_bone("c_" + ear + dupli):
                ear_bone = get_edit_bone("c_" + ear + dupli)
                if ear_bone.layers[22] == False:
                    ref_bone = get_edit_bone(ear + "_ref" + dupli)
                    copy_bone_transforms(ref_bone, ear_bone)

                    # ear parent
                    if ear == "ear_01":
                        if ref_bone.parent:

                            if "head_ref" in ref_bone.parent.name:
                                skull_bone = get_edit_bone(ref_bone.parent.name.replace('head_ref', 'c_skull_02'))
                                if skull_bone:
                                    ear_bone.parent = skull_bone
                                else:
                                    head_bone = get_edit_bone(ref_bone.parent.name.replace('head_ref', 'head'))
                                    ear_bone.parent = head_bone
                            else:
                                if ref_bone.parent.name[:-2][-4:] == "_ref":
                                    if get_edit_bone('c_' + ref_bone.parent.name.replace('_ref', '')):
                                        ear_bone.parent = get_edit_bone('c_' + ref_bone.parent.name.replace('_ref', ''))
                                    else:
                                        ear_bone.parent = get_edit_bone(get_first_master_controller())
                                else:
                                    ear_bone.parent = ref_bone.parent
                        else:
                            ear_bone.parent = get_edit_bone(get_first_master_controller())
        if "ear" in locals():
            del ear

    # if breast enabled
    if get_edit_bone('c_breast_01.l'):
        print('\n Aligning breasts...')
        breasts = ard.breast_bones

        for side in [".l", ".r"]:
            for bname in breasts:
                control_bone = get_edit_bone(bname + side)
                ref_bone = get_edit_bone(bname[2:] + "_ref" + side)

                if ref_bone and control_bone:
                    # set transforms
                    copy_bone_transforms(ref_bone, control_bone)

                    # set parents
                    # if the reference bones are parented to the spine bones, find the matching bone for the control bones parent
                  
                    if ref_bone.parent:
                        ref_parent_name = ref_bone.parent.name
                        
                        # root parent
                        if ref_parent_name == ard.spine_ref_dict['root']:
                            parent_name = ard.spine_bones_dict['root']
                            parent_bone = get_edit_bone(parent_name)
                            control_bone.parent = parent_bone
                        
                        else:
                            # spine parent
                            spine_idx = ard.get_spine_idx(ref_parent_name)                            
                            if spine_idx == None:
                                print("Error, could not find spine idx:", ref_parent_name)
                                
                            if spine_idx:
                                c_spine_bend_name = ard.get_spine_name('control_bend', spine_idx)
                                spine_name = ard.get_spine_name('base', spine_idx)
                                c_spine_name = ard.get_spine_name('control', spine_idx)
                                
                                parent_list = [c_spine_bend_name, spine_name, c_spine_name]
                                
                                for parent_name in parent_list:
                                    parent_bone = get_edit_bone(parent_name)
                                    if parent_bone:
                                        control_bone.parent = parent_bone
                                        break
                       
                    # if there's no parent assigned, find the default parent bone
                    else:
                        default_parent = get_edit_bone(ard.get_spine_name('control_bend', 2))#('c_spine_02_bend.x')
                        default_parent_traj = get_edit_bone(get_first_master_controller())
                        if default_parent:
                            control_bone.parent = default_parent
                        elif default_parent_traj:
                            control_bone.parent = default_parent_traj

                else:
                    if scn.arp_debug_mode:
                        print("No breasts found, skip it")

                        
    # switch pose state and mode
    bpy.ops.object.mode_set(mode='POSE')

    # set c_neck_01 pose mode params
    for dupli in limb_sides.head_sides:
        neck_01_pbone = get_pose_bone(c_neck_01_name[:-2] + dupli)
        neck_pbone = get_pose_bone(c_neck_name[:-2] + dupli)
        if neck_01_pbone:
            # Euler
            neck_01_pbone.rotation_mode = "XYZ"
            # custom shape
            neck_01_pbone.custom_shape = get_object("cs_torus_03")
            neck_01_pbone.bone.show_wire = True
            neck_01_pbone.custom_shape_transform = get_pose_bone(c_p_neck_01_name[:-2] + dupli)
            # group
            if neck_pbone:
                neck_01_pbone.bone_group = neck_pbone.bone_group

        # Subnecks
        for i in range(1, 17):
            # set shape
            cont_subneck = get_pose_bone('c_subneck_' + str(i) + dupli)
            if cont_subneck:
                if cont_subneck.custom_shape == None:
                    cont_subneck.custom_shape = get_object("cs_torus_03")

    if scn.arp_debug_mode == True:
        print("\n FINISH ALIGNING SPINE BONES...\n")

    if scn.arp_debug_mode == True:
        print("\n COPY BONES TO RIG ADD ")

    if rig.arp_secondary_type == "ADDITIVE" and rig_add:
        copy_bones_to_rig_add(rig, rig_add)

    if scn.arp_debug_mode == True:
        print("\n FINISHED COPYING TO RIG ADD ")

    # --END ALIGN SPINE BONES


def disable_proxy_picker():
    try:
        proxy_picker_state = bpy.context.scene.Proxy_Picker.active
        bpy.context.scene.Proxy_Picker.active = False
        return proxy_picker_state
    except:
        pass


def restore_proxy_picker(proxy_picker_state):
    try:
        bpy.context.scene.Proxy_Picker.active = proxy_picker_state
    except:
        pass


def switch_bone_layer(bone, base_layer, dest_layer, mirror):
    if bone[-2:] == ".x":
        mirror = False

    if mirror == False:
        if get_edit_bone(bone):
            get_edit_bone(bone).layers[dest_layer] = True
            get_edit_bone(bone).layers[base_layer] = False

    if mirror == True:
        if get_edit_bone(bone + ".l") and get_edit_bone(bone + ".r"):
            get_edit_bone(bone + ".l").layers[dest_layer] = True
            get_edit_bone(bone + ".l").layers[base_layer] = False
            get_edit_bone(bone + ".r").layers[dest_layer] = True
            get_edit_bone(bone + ".r").layers[base_layer] = False


def mirror_hack():
    bpy.ops.transform.translate(value=(0, 0, 0), orient_type='NORMAL')


def init_selection(bone_name):
    bpy.ops.object.mode_set(mode='EDIT')
    bpy.ops.armature.select_all(action='DESELECT')

    if (bone_name != "null"):
        bpy.context.active_object.data.edit_bones.active = bpy.context.active_object.data.edit_bones[bone_name]
        get_edit_bone(bone_name).select_head = True
        get_edit_bone(bone_name).select_tail = True


def set_draw_scale(name, size):
    bone = bpy.context.active_object.pose.bones[name + ".l"]
    set_custom_shape_scale(bone, size)


def is_facial_enabled(armature_object):   
    if armature_object.type == "ARMATURE":
        if armature_object.data.bones.get("jaw_ref.x"):
            return True
    return False


def copy_bones_to_rig_add(rig, rig_add):
    unhide_object(rig_add)

    bone_add_data = {}
    all_bones_data = {}
    edit_rig(rig)

    # make dictionnary of bones transforms in armature 1
    rig_add_bone_names = ard.arm_bones_rig_add + ard.leg_bones_rig_add + ard.spine_bones_rig_add

    for bone in rig.data.edit_bones:
        all_bones_data[bone.name] = (bone.head.copy(), bone.tail.copy(), bone.roll)

        bone_short_name = ""

        if not '_dupli_' in bone.name:
            bone_short_name = bone.name[:-2]
            if bone.name[-2:] == ".x":
                bone_short_name = bone.name
        else:
            bone_short_name = bone.name[:-12]
            if bone.name[-2:] == ".x":
                bone_short_name = bone_short_name + ".x"

        if bone_short_name in rig_add_bone_names:
            bone_add_data[bone.name] = (bone.head.copy(), bone.tail.copy(), bone.roll)

    if "bone" in locals():
        del bone

    # make sure rig_add collection is visible
    for collec in rig_add.users_collection:
        collec.hide_viewport = False

    edit_rig(rig_add)
    bpy.context.active_object.data.use_mirror_x = False

    # apply the bones transforms to the armature
    for b in bone_add_data:
        bone = get_edit_bone(b)
        if not bone:
            bone = bpy.context.active_object.data.edit_bones.new(b)
        bone.head, bone.tail, bone.roll = bone_add_data[bone.name]

    if "bone" in locals():
        del bone

    # foot_bend, hand_bend, waist_end and epaules_bend bones to block the skin area
    c_waist_bend_end = get_edit_bone('c_waist_bend_end.x')
    if c_waist_bend_end and 'c_spine_02_bend' in all_bones_data:
        c_waist_bend_end.head, c_waist_bend_end.tail, c_waist_bend_end.roll = all_bones_data['c_spine_02_bend.x']

    epaules_bend = get_edit_bone('epaules_bend.x')
    if epaules_bend == None and 'head.x' in all_bones_data:
        epaules_bend = bpy.context.active_object.data.edit_bones.new("epaules_bend.x")

    if epaules_bend and 'c_spine_02_bend.x' in all_bones_data:
        epaules_bend.head, epaules_bend.tail, epaules_bend.roll = all_bones_data['c_spine_02.x']
        if 'head.x' in all_bones_data:
            epaules_bend.tail = all_bones_data['head.x'][1]

        # disable epaules_bend deform if secondary controllers are not additive
        if rig.arp_secondary_type == "NONE" or rig.arp_secondary_type == "TWIST_BASED":
            epaules_bend.use_deform = False
        else:
            epaules_bend.use_deform = True

    if len(limb_sides.leg_sides) > 0:
        for side in limb_sides.leg_sides:
            foot_bend = get_edit_bone('c_foot_bend' + side)
            if not foot_bend:
                foot_bend = bpy.context.active_object.data.edit_bones.new('c_foot_bend' + side)
            if 'foot' + side in all_bones_data:
                foot_bend.head, foot_bend.tail, foot_bend.roll = all_bones_data['foot' + side]

        if "side" in locals():
            del side

    if len(limb_sides.arm_sides) > 0:
        for side in limb_sides.arm_sides:
            hand_bend = rig_add.data.edit_bones.get('hand_bend' + side)
            if not hand_bend:
                hand_bend = bpy.context.active_object.data.edit_bones.new('hand_bend' + side)
            if 'hand' + side in all_bones_data:
                hand_ref_head, hand_ref_tail, hand_ref_roll = all_bones_data['hand' + side]
                hand_bend.head, hand_bend.tail, hand_bend.roll = all_bones_data['hand' + side]
                hand_bend.head += (hand_ref_tail - hand_ref_head) * 0.2
                hand_bend.tail += (hand_ref_tail - hand_ref_head) * 0.2

        if "side" in locals():
            del side

    null_bend = rig_add.data.edit_bones.get('null_bend.x')
    c_thigh_bend_contact_r = rig_add.data.edit_bones.get('c_thigh_bend_contact.r')
    c_thigh_bend_contact_l = rig_add.data.edit_bones.get('c_thigh_bend_contact.l')
    c_waist_bend = rig_add.data.edit_bones.get('c_waist_bend.x')

    if null_bend == None:
        print('null_bend is missing, create it')
        null_bend = rig_add.data.edit_bones.new("null_bend.x")

    if c_thigh_bend_contact_l and c_thigh_bend_contact_r and c_waist_bend and null_bend:
        null_bend.head = (c_thigh_bend_contact_r.head + c_thigh_bend_contact_l.head) * 0.5
        null_bend.tail = null_bend.head + (c_waist_bend.tail - c_waist_bend.head)

        # disable c_waist_bend and null_bend deform if secondary controllers are not additive
        if rig.arp_secondary_type == "NONE" or rig.arp_secondary_type == "TWIST_BASED":
            c_waist_bend.use_deform = False
            null_bend.use_deform = False
        else:
            c_waist_bend.use_deform = True
            null_bend.use_deform = True

    # Make sure constraints are ok
    bpy.ops.object.mode_set(mode='POSE')
    for b in rig_add.pose.bones:
        if rig.data.bones.get(b.name):
            cns = None
            if len(b.constraints) != 0:
                cns = b.constraints[0]
            else:
                cns = b.constraints.new("COPY_TRANSFORMS")

            cns.target = rig
            cns.subtarget = b.name
            cns.target_space = 'LOCAL'
            cns.owner_space = 'LOCAL'

    if "b" in locals():
        del b

    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.select_all(action='DESELECT')
    hide_object(rig_add)
    bpy.context.view_layer.objects.active = rig
    bpy.ops.object.mode_set(mode='POSE')


def edit_rig(_rig):
    try:
        bpy.ops.object.mode_set(mode='OBJECT')
        bpy.ops.object.select_all(action='DESELECT')
    except:
        pass
    unhide_object(_rig)
    _rig.hide_select = False
    _rig.select_set(state=1)
    bpy.context.view_layer.objects.active = _rig

    bpy.ops.object.mode_set(mode='EDIT')


def set_breast(breast_state):
    current_mode = bpy.context.mode
    rig = get_object(bpy.context.active_object.name)
    
    # disable the proxy picker to avoid bugs
    proxy_picker_state = disable_proxy_picker()

    bpy.ops.object.mode_set(mode='EDIT')

    breasts = ["breast_01", "breast_02"]

    for breast_name in breasts:
        for side in [".l", ".r"]:
            
            breast_ref_name = breast_name + "_ref" + side
            c_breast_name = "c_" + breast_name + side
            breast_proxy_name = "c_" + breast_name + "_proxy" + side
            
            # disabled, delete bones
            if not breast_state:                
                c_breast = get_edit_bone(c_breast_name)
                if c_breast:
                    delete_edit_bone(c_breast)                    
                
                breast_ref = get_edit_bone(breast_ref_name)
                if breast_ref:
                    delete_edit_bone(breast_ref)

                # switch proxy bones layer                
                switch_bone_layer(breast_proxy_name, 1, 22, False)

            # enabled, create bones
            else:
                b_ref = get_edit_bone(breast_ref_name)
                b_control = get_edit_bone(c_breast_name)

                if b_ref == None:
                    b_ref = rig.data.edit_bones.new(breast_ref_name)
                if b_control == None:
                    b_control = rig.data.edit_bones.new(c_breast_name)

                fac = 1
                if side == ".r":
                    fac = -1
                
                spine_count = rig.rig_spine_count
                last_spine_idx = spine_count - 1
                str_idx = '%02d' % last_spine_idx
                
                spine_last_ref_name = 'spine_' + str_idx + '_ref.x'
                spine_last_ref = get_edit_bone(spine_last_ref_name)
                c_traj = get_edit_bone(get_first_master_controller())

                if breast_name == 'breast_01':
                    # set bone transforms
                    if spine_last_ref:
                        b_ref.head = spine_last_ref.head.copy()
                        # set x pos
                        b_ref.head += (spine_last_ref.x_axis.normalized() * fac * spine_last_ref.length * 0.5)
                        # set y pos
                        b_ref.head += (spine_last_ref.z_axis.normalized() * spine_last_ref.length * 1)
                        b_ref.tail = b_ref.head + (spine_last_ref.tail - spine_last_ref.head) * 0.25

                    else:
                        if c_traj:
                            b_ref.head = [c_traj.length * 0.5 * fac, 0, 0]
                            b_ref.tail = b_ref.head + Vector((0, 0, c_traj.length * 0.2))
                        else:
                            b_ref.head = [fac, 0, 0]
                            b_ref.tail = b_ref.head + Vector((0, 0, 1.0))

                    b_ref.roll = radians(90 * fac)

                if breast_name == 'breast_02':
                    breast_01_ref_name = 'breast_01_ref' + side
                    # set bone transforms
                    breast_01_ref = get_edit_bone(breast_01_ref_name)
                    b_ref.head = breast_01_ref.tail + (breast_01_ref.x_axis.normalized() * fac * breast_01_ref.length * 0.5)
                    b_ref.tail = b_ref.head + (breast_01_ref.tail - breast_01_ref.head)
                    b_ref.roll = breast_01_ref.roll

                b_control.head, b_control.tail, b_control.roll = b_ref.head, b_ref.tail, b_ref.roll

                # set default parent
                if spine_last_ref:
                    b_ref.parent = spine_last_ref
    
                # set deform
                b_ref.use_deform = False

                # Set layers       
                set_bone_layer(b_ref, 17)
                set_bone_layer(b_control, 1)             

                # move proxy bone layer
                switch_bone_layer(breast_proxy_name, 22, 1, False)

                # Set custom shapes and groups
                bpy.ops.object.mode_set(mode='POSE')
                
                c_breast_pb = get_pose_bone(c_breast_name)
                breast_ref_pb = get_pose_bone(breast_ref_name)

                grp = bpy.context.active_object.pose.bone_groups.get('body' + side[-2:])
                c_breast_pb.bone_group = grp
                breast_ref_pb.bone_group = grp
                cs = None

                if breast_name == 'breast_01':
                    if side == ".l":
                        cs_name = 'cs_semi_sphere'
                    else:
                        cs_name = 'cs_semi_sphere_inv'

                    if get_object(cs_name) == None:
                        append_from_arp(nodes=[cs_name], type="object")

                    cs = get_object(cs_name)
                    set_custom_shape_scale(c_breast_pb, 4.0)

                if breast_name == 'breast_02':
                    if get_object("cs_arrow_02") == None:
                        append_from_arp(nodes=["cs_arrow_02"], type="object")
                        
                    cs = get_object("cs_arrow_02")

                c_breast_pb.custom_shape = cs

                get_data_bone(c_breast_name).show_wire = True

                bpy.ops.object.mode_set(mode='EDIT')


    # restore saved mode
    restore_current_mode(current_mode)

    # restore picker
    restore_proxy_picker(proxy_picker_state)

    return None
    # end set_breast()


def spline_ik_clear_constraints(stretch_bone_name, side):
    # Clear existing constraints to prevent dependency cycles during the bones setup

    # get spline name
    name = stretch_bone_name.split('_')[0]

        # armature
    for i in range(0, 1025):
        id = '%02d' % i
        bname = "c_"+name+"_inter_"+id+side
        b = get_pose_bone(bname)
        if b == None:
            continue
        cns = b.constraints.get("Armature")
        if cns:
            b.constraints.remove(cns)
            remove_invalid_drivers()

        # spline IK
    for i in range(1, 1025):
        id = '%02d' % i
        bname = name+"_" + id + side
        b = get_pose_bone(bname)
        if b == None:
            continue
        splineik_cns = b.constraints.get("Spline IK")
        if splineik_cns:
            b.constraints.remove(splineik_cns)
            remove_invalid_drivers()

        # spline individual rotation
    for i in range(1, 1025):
        id = '%02d' % (i)
        bname = name+"_" + id + side
        b = get_pose_bone(bname)
        if b == None:
            continue
        copy_cns = b.constraints.get("Copy Rotation")
        if copy_cns:
            b.constraints.remove(copy_cns)

        # curviness
    for i in range(1, 1025):  # the first and last stay in place
        id = '%02d' % (i)
        bname = "c_"+name+"_"+id+side
        b = get_pose_bone(bname)
        if b == None:
            continue
        copy_cns = b.constraints.get("Copy Transforms")
        if copy_cns:
            b.constraints.remove(copy_cns)

        # stretch bone
    stretch_pbone = get_pose_bone(stretch_bone_name)
    if stretch_pbone:
        damp_cns = stretch_pbone.constraints.get("Damped Track")
        if damp_cns:
            stretch_pbone.constraints.remove(damp_cns)

        stretch_cns = stretch_pbone.constraints.get("Stretch To")
        if stretch_cns:
            stretch_pbone.constraints.remove(stretch_cns)

    remove_invalid_drivers()
    # end spline_ik_clear_constraints()


def set_spline_ik(amount, type='1', cont_freq=2, interpolation='SMOOTH', bbones_count=0, side_arg=None, spline_parent_master="stretch", spline_parent_last="c_spline_tip", spline_parent_last_master="c_spline_root", new_name="spline", new_side=None, smoothness=3, deform=True, update_transforms=True):
    print("Set Spline IK...")
    scn = bpy.context.scene

    # safety
    xmirror_state = bpy.context.object.data.use_mirror_x
    bpy.context.object.data.use_mirror_x = False
    bpy.context.scene.tool_settings.use_keyframe_insert_auto = False

    # enable all layers
    layers_select = enable_all_armature_layers()

    # get side
    if side_arg == None:
        side_arg = ".x"

    # get current name
    name = get_spline_name(side_arg)
    
   
    # get the existing limbs
    limb_sides.get_multi_limbs()

    # define the newly set side if any, and set the renamed side
    renamed_side = None
    if new_side:
        if new_side != side_arg[-2:]:# .l != .x
            dupli_id, found_base = get_next_dupli_id(new_side, 'spline_ik')
            if not found_base:
                renamed_side = new_side
            else:
                renamed_side = '_dupli_' + dupli_id + new_side

    stretch_bone_name = name+"_stretch" + side_arg

    # --Pose Mode--
    bpy.ops.object.mode_set(mode='POSE')
    spline_ik_clear_constraints(stretch_bone_name, side_arg)

    # --Edit Mode--
    bpy.ops.object.mode_set(mode='EDIT')

    # Define c_spline_masters
    spline_masters_data = {}# master_idx: control_idx
    spline_inters_data = {}# spline_idx: control_idx

    if type == '2':
        segment = 1
        master_idx = 1

        for i in range(1, amount+2):
            is_master = False

            # last one
            if i == amount+1 and segment != 1:
                spline_masters_data[master_idx] = i
                is_master = True

            if segment > cont_freq:
                segment = 1

            if segment == 1:
                spline_masters_data[master_idx] = i
                master_idx += 1
                is_master = True

            spline_inters_data[i] = master_idx-1#store the corresponding last registered master idx

            segment += 1

        #print("Spline Masters Data", spline_masters_data)

    print("  set bones")
    # look for the existing spline ik bones if any
    root_pos = Vector((0, 0, 0))
    tip_pos = Vector((0, 0, 1))
    root_bone = get_edit_bone(name+"_01_ref" + side_arg)
    tip_bone = None
    spline_vec = Vector((0, 0, 1))

    if root_bone:
        root_pos = root_bone.head.copy()
        # look for the tip bone
        for i in range(1, 1024):
            id = '%02d' % i
            supposed_tip_bone = get_edit_bone(name+"_" + id + "_ref" + side_arg)
            if supposed_tip_bone:
                tip_bone = supposed_tip_bone

    if tip_bone:
        tip_pos = tip_bone.tail.copy()
        spline_vec = tip_bone.tail - root_bone.head

    # Remove Curvy controller if type 2
    if type == '2':
        curvy_name = "c_"+name+"_curvy"+side_arg
        curvy = get_edit_bone(curvy_name)
        if curvy:
            delete_edit_bone(curvy)

    # Remove bones out of range
        # ref bones, spline ik
    for i in range(amount + 1, 1025):
        id = '%02d' % i
        c_id = '%02d' % (i + 1) # we need one more control bone at the tip
        # ref bones
        ref_name = name+"_"+id+"_ref"+side_arg
        ref_bone = get_edit_bone(ref_name)
        if ref_bone:
            delete_edit_bone(ref_bone)
        # spline ik bones
        spname = name+"_"+id+side_arg
        spline_ik_bone = get_edit_bone(spname)
        if spline_ik_bone:
            delete_edit_bone(spline_ik_bone)

    #   controllers -masters
    if type == '1':
        min = 1# no c_spline_master for type 1, remove all
        max = 1025
    elif type == '2':
        min = len(spline_masters_data)+1
        max = 1025

    for i in range(min, max):
        c_id = '%02d' % i
        c_name = "c_"+name+"_master_"+c_id+side_arg
        c_bone = get_edit_bone(c_name)
        if c_bone:
            delete_edit_bone(c_bone)

    #   controllers -inters
    if type == '1':
        min = 1
        max = 1025
    elif type == '2':
        min = spline_inters_data[len(spline_inters_data)]
        max = 1025
    for i in range(min, max):
        c_id = '%02d' % i
        c_name = "c_"+name+"_inter_"+c_id+side_arg
        c_bone = get_edit_bone(c_name)
        if c_bone:
            delete_edit_bone(c_bone)

    #   controllers -individuals
    for i in range(amount+ 1, 1025):
        c_id = '%02d' % (i + 1)# we need one more control bone at the tip
        if type == '2':
            c_id = '%02d' % i# extra tip not used for the second type

        cname = "c_"+name+"_"+c_id+side_arg
        control_bone = get_edit_bone(cname)
        if control_bone:
            delete_edit_bone(control_bone)


    # Create bones
    controllers_list = []# store controllers in a list for convenience
    # -ref bones
    bone_length = spline_vec.magnitude / amount
    ref_bones_dict = {}
    spline_ik_bones = []

    for i in range(1, amount+1):
        id = '%02d' % i
        prev_id = '%02d' % (i-1)
        # names
        ref_bone_name = name + "_" + id + "_ref" + side_arg
        spline_ik_bones.append(ref_bone_name)
        ref_bone = get_edit_bone(ref_bone_name)
        existing_bone = True
        if ref_bone == None:
            ref_bone = bpy.context.active_object.data.edit_bones.new(ref_bone_name)
            ref_bone["arp_duplicate"] = True
            existing_bone = False
        ref_bone.use_deform = False
        # coords
        if not existing_bone or update_transforms:
            ref_bone.head = root_pos + ((bone_length * (i - 1)) * spline_vec.normalized())
            ref_bone.tail = ref_bone.head + (bone_length * spline_vec.normalized())
        # save in a dict for later use
        ref_bones_dict[ref_bone_name] = ref_bone.head.copy(), ref_bone.tail.copy(), ref_bone.roll
        # parent
        if ref_bone.parent == None:
            if i == 1:
                ref_bone.parent = get_edit_bone(get_first_master_controller())
            else:
                ref_bone.parent = get_edit_bone(name + "_" + prev_id + "_ref" + side_arg)
                ref_bone.use_connect = True
        # layer
        set_bone_layer(ref_bone, 17)

        # tag with a custom prop
        ref_bone["arp_spline"] = new_name

        # always update transforms on bones count change
        if "spline_count" in ref_bone.keys():
            previous_amount = ref_bone["spline_count"]
            if previous_amount != amount:
                update_transforms = True


    # store the params in the root bone properties
    ref_bone_1_name = name + "_01_ref"+side_arg
    ref_bone_1 = get_edit_bone(ref_bone_1_name)
    ref_bone_1["spline_count"] = amount
    ref_bone_1["spline_cont_freq"] = cont_freq
    ref_bone_1["spline_bbones"] = bbones_count
    ref_bone_1["spline_parent_master"] = spline_parent_master
    ref_bone_1["spline_parent_last"] = spline_parent_last
    ref_bone_1["spline_parent_last_master"] = spline_parent_last_master
    ref_bone_1["spline_name"] = new_name
    ref_bone_1["spline_smoothness"] = smoothness
    ref_bone_1["spline_update_transforms"] = update_transforms
    ref_bone_1["spline_deform"] = deform
    ref_bone_1["spline_type"] = type
    ref_bone_1["spline_masters_data"] = dict_to_string(spline_masters_data)
    ref_bone_1["spline_inters_data"] = dict_to_string(spline_inters_data)
    ref_bone_1["spline_interpolation"] = interpolation
    
    
    # -controller (root bone)
    root_bone_name = "c_" + name + "_root" + side_arg
    root_bone = get_edit_bone(root_bone_name)
    spline_ik_bones.append(root_bone_name)
    if root_bone == None:
        # create
        root_bone = create_edit_bone(root_bone_name)      
        # setup with generic coordinates, will be aligned later
        root_bone.head, root_bone.tail = [0, 0, 0], [0, 0, 1]
        # parent
    if root_bone.parent == None:
        root_bone.parent = get_edit_bone(get_first_master_controller())
        # layer
        set_bone_layer(root_bone, 0)
    # tag with custom prop
    root_bone["arp_spline"] = new_name

    controllers_list.append(root_bone.name)

    # -spline ik chain
    for i in range(1, amount + 1):
        # create bones
        id = '%02d' % i
        prev_id = '%02d' % (i - 1)
        bone_name = name + "_" + id + side_arg
        spline_ik_bones.append(bone_name)
        bone = get_edit_bone(bone_name)
        if bone == None:
            bone = create_edit_bone(bone_name)
            # layer
            set_bone_layer(bone, 8)
        # bendybones
        if type == '1':
            bone.bbone_segments = bbones_count
        elif type == '2':
            bone.bbone_segments = 1
            
        # setup with generic coordinates, will be aligned later
        bone.head, bone.tail = [0, 0, 0 + i], [0, 0, 1 + i]# bones are parented and connected, they must be offset to avoid automatic deletion
        # relation
        if i == 1:
            bone.parent = get_edit_bone(name + "_root" + side_arg)
        else:
            bone.parent = get_edit_bone(name + "_" + prev_id + side_arg)
            bone.use_connect = True
        # set deform
        if type == '1':
            bone.use_deform = deform
        elif type == '2':
            bone.use_deform = False

    # -stretch bone
    stretch_bone = get_edit_bone(stretch_bone_name)
    spline_ik_bones.append(stretch_bone_name)
    if stretch_bone == None:
        stretch_bone = create_edit_bone(stretch_bone_name)
        # layer
        set_bone_layer(stretch_bone, 8)
        # set deform
        stretch_bone.use_deform = False
        # setup with generic coordinates, will be aligned later
        stretch_bone.head, stretch_bone.tail = [0, 0, 0], [0, 0, 1]        
    # relation
    stretch_bone.parent = get_edit_bone("c_" + name + "_root" + side_arg)
        

    # -controllers (masters)
    if type == '2':
        for master_i in spline_masters_data:
            master_idx = '%02d' % master_i
            master_name = "c_" + name + "_master_" + master_idx + side_arg
            master = get_edit_bone(master_name)
            spline_ik_bones.append(master_name)
            # create
            if master == None:
                master = create_edit_bone(master_name)
                # layer
                set_bone_layer(master, 0)
                #print("CREATE MASTER", master.name)
            # setup with blank coordinates, will be aligned later
            master.head, master.tail = [0, 0, 0 + i], [0, 0, 1+i]
            
            # no deform
            master.use_deform = False
            # tag with custom prop
            master["arp_spline"] = new_name

            controllers_list.append(master.name)


    # -controllers (inter)
    if type == '2':
        for inter_i in spline_inters_data:
            id = '%02d' % inter_i
            c_bone_name = "c_" + name + "_inter_" + id + side_arg
            spline_ik_bones.append(c_bone_name)
            c_bone = get_edit_bone(c_bone_name)
            # create
            if c_bone == None:
                c_bone = create_edit_bone(c_bone_name)
                # layer
                set_bone_layer(c_bone, 0)
            # set deform
            c_bone.use_deform = False
            # setup with blank coordinates, will be aligned later
            c_bone.head, c_bone.tail = [0, 0, 0 + i], [0, 0, 1+i]# bones are parented and connected, they must be offset to avoid automatic deletion
            
            # tag with custom prop
            c_bone["arp_spline"] = new_name

            controllers_list.append(c_bone.name)

    # -controllers (individuals)
    max_range = amount+2# +2, we need one more for the tip
    if type == '2':
        max_range = amount+1# no extra needed for the second type
    for i in range(1, max_range):
        id = '%02d' % i
        prev_id = '%02d' % (i - 1)
        next_id = '%02d' % (i + 1)
        c_bone_name = "c_" + name + "_" + id + side_arg
        c_bone_prev_name = "c_" + name + "_" + prev_id + side_arg
        c_bone_next_name = "c_" + name + "_" + next_id + side_arg
        
        spline_ik_bones.append(c_bone_name)
        
        c_bone = get_edit_bone(c_bone_name)
        # create
        if c_bone == None:
            c_bone = bpy.context.active_object.data.edit_bones.new(c_bone_name)
            # layer
            set_bone_layer(c_bone, 0)            
            
        # set deform
        if type == '1':
            c_bone.use_deform = False
        elif type == '2':
            c_bone.use_deform = deform
        # setup with blank coordinates, will be aligned later
        c_bone.head, c_bone.tail = [0, 0, 0 + i], [0, 0, 1+i]# bones are parented and connected, they must be offset to avoid automatic deletion
        
        # tag with custom prop
        c_bone["arp_spline"] = new_name

        controllers_list.append(c_bone.name)
        
    #    controller (inviduals): set bendy-bones settings in a second loop after creation
    max_range = amount+2# +2, we need one more for the tip
    if type == '2':
        max_range = amount+1# no extra needed for the second type
    for i in range(1, max_range):
        id = '%02d' % i
        prev_id = '%02d' % (i - 1)
        next_id = '%02d' % (i + 1)
        c_bone_name = "c_" + name + "_" + id + side_arg
        c_bone_prev_name = "c_" + name + "_" + prev_id + side_arg
        c_bone_next_name = "c_" + name + "_" + next_id + side_arg
        
        c_bone = get_edit_bone(c_bone_name)        
            
        # bendybones settings
        if type == '1' or bbones_count == 0 or bbones_count == 1:
            c_bone.bbone_segments = 1
            c_bone.bbone_handle_type_start = c_bone.bbone_handle_type_end = 'AUTO'
            c_bone.bbone_custom_handle_start = None
            c_bone.bbone_custom_handle_end = None
        elif type == '2':
            c_bone.bbone_segments = bbones_count            
            c_bone.bbone_handle_type_start = c_bone.bbone_handle_type_end = 'ABSOLUTE'
            c_bone.bbone_custom_handle_start = get_edit_bone(c_bone_prev_name)
            c_bone.bbone_custom_handle_end = get_edit_bone(c_bone_next_name)
            

    # -controllers (tip)
    tip_name = "c_" + name + "_tip" + side_arg
    c_tip = get_edit_bone(tip_name)
    spline_ik_bones.append(tip_name)
    if c_tip == None:
        # create
        c_tip = create_edit_bone(tip_name)
        # layer
        set_bone_layer(c_tip, 0)
        # set deform
        c_tip.use_deform = False
        # setup with generic coordinates, will be aligned later
        c_tip.head, c_tip.tail = [0, 0, 0], [0, 0, 1]
        # relation
    if spline_parent_last_master != "none":
        c_tip.parent = get_edit_bone(spline_parent_last_master + side_arg)
        
        # tag with custom prop
    c_tip["arp_spline"] = new_name

    controllers_list.append(c_tip.name)
    
    # -controllers (curvy)
    if type == '1':# only for type 1
        curvy_name = "c_" + name + "_curvy" + side_arg
        c_curvy = get_edit_bone(curvy_name)
        spline_ik_bones.append(curvy_name)
        if c_curvy == None:
            # create
            c_curvy = create_edit_bone(curvy_name)
            # set deform
            c_curvy.use_deform = False
            # setup with generic coordinates, will be aligned later
            c_curvy.head, c_curvy.tail = [0, 0, 0], [0, 0, 1]
            # relation
            c_curvy.parent = get_edit_bone(name + "_stretch" + side_arg)
            # layer
            set_bone_layer(c_curvy, 0)
            controllers_list.append(c_curvy.name)

        # tag with custom prop
        c_curvy["arp_spline"] = new_name

    # Align bones
    align_spline_ik_bones(name, side_arg)

    print("  set curve")

    # --Pose Mode--
    bpy.ops.object.mode_set(mode='POSE')

    # reset pose
        # store active pose
    bpy.ops.pose.select_all(action='SELECT')
    controllers_saved_transforms = save_pose()
        # reset
    auto_rig_reset.reset_all()

    reset_spline_stretch_ctrl(name, side_arg)

    # --Object Mode--
    bpy.ops.object.mode_set(mode='OBJECT')
    rig_name = bpy.context.active_object.name
    arp_armature = bpy.data.objects.get(rig_name)

    # Set the NurbsCurve
    # create
    nurbs = create_spline_nurbs(_amount=amount, _arp_armature=arp_armature, _side_arg=side_arg, _smoothness=smoothness)
    # align points to bones
    new_spline = nurbs.data.splines[0]
    align_spline_curve(new_spline, ref_bones_dict)

    # add hook modifiers to controllers
    set_spline_hooks(spline=nurbs, armature=arp_armature, length=amount, freq=cont_freq, interpolation=interpolation, type=type, spline_masters_data=spline_masters_data, spline_inters_data=spline_inters_data, side=side_arg, name=name)

    set_active_object(arp_armature.name)

    hide_object(nurbs)

    # --Pose Mode--
    bpy.ops.object.mode_set(mode='POSE')

    print("  set constraints")

    # constraint: spline IK
    #   add a spline ik constraint to the last bone
    id = '%02d' % (amount)
    last_bone_name = name + "_" + id + side_arg
    last_pbone = get_pose_bone(last_bone_name)
    splineik_cns = last_pbone.constraints.get("Spline IK")
    if splineik_cns == None:
        splineik_cns = last_pbone.constraints.new("SPLINE_IK")
        splineik_cns.name = "Spline IK"
    splineik_cns.target = bpy.data.objects.get(nurbs.name)

    splineik_cns.chain_count = amount
    splineik_cns.use_even_divisions = False
    splineik_cns.use_chain_offset = False
    splineik_cns.use_curve_radius = True
    splineik_cns.y_scale_mode = 'FIT_CURVE'
    splineik_cns.xz_scale_mode = 'VOLUME_PRESERVE'
    splineik_cns.use_original_scale = True
    splineik_cns.bulge = 1.0

    #   set pbone properties as drivers
    root_name = "c_" + name + "_root" + side_arg
    root_pbone = get_pose_bone(root_name)

    stretch_prop_found = False
    if len(root_pbone.keys()):
        if "stretch_mode" in root_pbone.keys():
            stretch_prop_found = True

    if not stretch_prop_found:
        create_custom_prop(node=root_pbone, prop_name="stretch_mode", prop_val=3, prop_min=0, prop_max=3, prop_description='None, Bone Original, Inverse Scale, Volume Preservation')

    volume_prop_found = False
    if len(root_pbone.keys()):
        if "volume_variation" in root_pbone.keys():
            volume_prop_found = True

    if not volume_prop_found:
        create_custom_prop(node=root_pbone, prop_name="volume_variation", prop_val=1.0, prop_min=0.0, prop_max=50.0, prop_description='High value = high stretch and squash, low value = no stretch and squash', soft_min=0.0, soft_max=10.0)

    y_scale_found = False
    if len(root_pbone.keys()):
        if "y_scale" in root_pbone.keys():
            y_scale_found = True

    if not y_scale_found:
        create_custom_prop(node=root_pbone, prop_name="y_scale", prop_val=2, prop_min=0, prop_max=2, prop_description='None, Fit Curve, Bone Original', soft_min=0, soft_max=2)

    #   set y_scale driver
    dp_length = 'pose.bones["' + last_bone_name + '"].constraints["Spline IK"].y_scale_mode'
    dr = bpy.context.active_object.animation_data.drivers.find(dp_length)
    if dr == None:
        dr = bpy.context.active_object.driver_add(dp_length, -1)
        dr.driver.expression = 'length'
    length_var = dr.driver.variables.get("length")
    if length_var == None:
        length_var = dr.driver.variables.new()
        length_var.name = "length"
        length_var.type = "SINGLE_PROP"
        length_var.targets[0].id = bpy.context.active_object
        length_var.targets[0].data_path = 'pose.bones["' + root_name + '"]["y_scale"]'

    #   set XZ scale driver
    dp_xz = 'pose.bones["' + last_bone_name + '"].constraints["Spline IK"].xz_scale_mode'
    dr = bpy.context.active_object.animation_data.drivers.find(dp_xz)
    if dr == None:
        dr = bpy.context.active_object.driver_add(dp_xz, -1)
        dr.driver.expression = 'stretch'
    stretch_var = dr.driver.variables.get("stretch")
    if stretch_var == None:
        stretch_var = dr.driver.variables.new()
        stretch_var.name = "stretch"
        stretch_var.type = "SINGLE_PROP"
        stretch_var.targets[0].id = bpy.context.active_object
        stretch_var.targets[0].data_path = 'pose.bones["' + root_name + '"]["stretch_mode"]'

    #   set bulge driver
    dp_bulge = 'pose.bones["' + last_bone_name + '"].constraints["Spline IK"].bulge'
    dr = bpy.context.active_object.animation_data.drivers.find(dp_bulge)
    if dr == None:
        dr = bpy.context.active_object.driver_add(dp_bulge, -1)
        dr.driver.expression = 'bulge'
    bulge_var = dr.driver.variables.get("bulge")
    if bulge_var == None:
        bulge_var = dr.driver.variables.new()
        bulge_var.name = "bulge"
        bulge_var.type = "SINGLE_PROP"
        bulge_var.targets[0].id = bpy.context.active_object
        bulge_var.targets[0].data_path = 'pose.bones["' + root_name + '"]["volume_variation"]'

    # constraint: invidual controllers rotation
    for i in range(1, amount + 1):
        id = '%02d' % (i)
        bname = name + "_" + id + side_arg
        b = get_pose_bone(bname)
        copy_cns = b.constraints.get("Copy Rotation")
        if type == '1':
            if copy_cns == None:
                copy_cns = b.constraints.new("COPY_ROTATION")
                copy_cns.name = "Copy Rotation"
            copy_cns.target = arp_armature
            copy_cns.subtarget = "c_" + name + "_" + id + side_arg
        elif type == '2':# no rot constraints for the second type
            if copy_cns:
                b.constraints.remove(copy_cns)

    # Twist custom props on c_spline
    for i in range(1, amount + 1):
        id = '%02d' % i
        c_name = "c_"+name+"_"+id+side_arg
        c_bone = get_pose_bone(c_name)
        spline_bone = get_pose_bone(name+"_"+id+side_arg)
        spline_bone.rotation_mode = "XYZ"
        # create custom prop
        prop_found = False
        if len(c_bone.keys()):
            if "twist" in c_bone.keys():
                prop_found = True
        if not prop_found:
            create_custom_prop(node=c_bone, prop_name="twist", prop_val=0.0, prop_min=-50.0, prop_max=50.0, prop_description='Bone twist, rotation along the Y axis')

        # set driver
        dp = 'pose.bones["' + spline_bone.name + '"].rotation_euler'
        dr = arp_armature.animation_data.drivers.find(dp, index=1)

        if type == '1':
            if dr:
                arp_armature.data.driver_remove(dp, -1)

        elif type == '2':# only compatible with type 2
            if dr == None:
                dr = arp_armature.driver_add(dp, 1)
                dr.driver.expression = 'var'
            var = dr.driver.variables.get("var")
            if var == None:
                var = dr.driver.variables.new()
                var.name = "var"
                var.type = "SINGLE_PROP"
                var.targets[0].id = arp_armature
                var.targets[0].data_path = 'pose.bones["' + c_bone.name + '"]["twist"]'

    # constraint: curvy    
    for i in range(2, amount + 1):  # the first and last stay in place
        id = '%02d' % (i)
        bname = "c_" + name + "_" + id + side_arg
        b = get_pose_bone(bname)
        copy_cns = b.constraints.get("Copy Transforms")
        
        if type == '1':# only for type 1
            if copy_cns == None:
                copy_cns = b.constraints.new("COPY_TRANSFORMS")
                copy_cns.name = "Copy Transforms"
            copy_cns.target = arp_armature
            copy_cns.subtarget = "c_" + name + "_curvy" + side_arg
            mid = (amount + 2) / 2
            dist = abs(mid - i)
            copy_cns.influence = sin((1 - (dist / (mid - 1))) * 1.57)
            copy_cns.mix_mode = 'BEFORE'
            copy_cns.target_space = copy_cns.owner_space = "LOCAL"
            
        elif type == '2':# no curvy cns in advanced mode
            if copy_cns:
                b.constraints.remove(copy_cns)
        

    # constraint: stretch bone
        # damped track
    stretch_pbone = get_pose_bone(stretch_bone_name)
    stretch_pbone.rotation_mode = "XYZ"
    damp_cns = stretch_pbone.constraints.get("Damped Track")
    if damp_cns == None:
        damp_cns = stretch_pbone.constraints.new("DAMPED_TRACK")
        damp_cns.name = "Damped Track"
    damp_cns.target = arp_armature
    damp_cns.subtarget = "c_" + name + "_tip" + side_arg

        # stretch to
    stretch_cns = stretch_pbone.constraints.get("Stretch To")
    if stretch_cns == None:
        stretch_cns = stretch_pbone.constraints.new("STRETCH_TO")
        stretch_cns.name = "Stretch To"
    stretch_cns.target = arp_armature
    stretch_cns.subtarget = "c_" + name + "_tip" + side_arg
    stretch_cns.volume = 'NO_VOLUME'

    # constraint: inters
    if type == '2':
        uneven = amount % cont_freq != 0

        for inter_i in spline_inters_data:
            id = '%02d' % inter_i
            bname = "c_"+name+"_inter_"+id+side_arg
            b = get_pose_bone(bname)
            master_i = spline_inters_data[inter_i]
            master_id = '%02d' % master_i
            next_master_id = '%02d' % (master_i+1)
            master_name = "c_"+name+"_master_"+master_id+side_arg
            next_master_name = "c_"+name+"_master_"+next_master_id+side_arg
            arm_cns = b.constraints.get("Armature")
            if arm_cns == None:
                arm_cns = b.constraints.new("ARMATURE")
                arm_cns.name = "Armature"

            # weight 1
            t = None
            if len(arm_cns.targets) == 0:
                t = arm_cns.targets.new()
            else:
                t = arm_cns.targets[0]
            t.target = arp_armature

            reached_tip = False
            if uneven:
                if inter_i == amount+1:
                    reached_tip = True

            if reached_tip:# in uneven case, tip should be the last master, mandatory
                t.subtarget = next_master_name
            else:
                t.subtarget = master_name

            r = (inter_i-1)%cont_freq
            fac = r/cont_freq

            # ensure the last segment is properly weighted, in case of uneven controller frequency
            last_segment = False
            if uneven:
                if master_i >= len(spline_masters_data)-1:
                    last_segment = True

            if last_segment:
                dist = abs(inter_i-len(spline_inters_data))
                tot = amount%cont_freq
                fac = 1-(dist/tot)

            if interpolation == "LINEAR":
                power = fac
            elif interpolation == "SMOOTH":
                power = smooth_interpolate(fac)

            if reached_tip:
                t.weight = 1.0
            else:
                t.weight = 1-power

            # weight 2
            # only if not tip
            t2 = None
            if inter_i != amount+1:
                if len(arm_cns.targets) <= 1:
                    t2 = arm_cns.targets.new()
                else:
                    t2 = arm_cns.targets[1]
                t2.target = arp_armature
                t2.subtarget = next_master_name
                t2.weight = power
            else:# tip, no second weight
                if len(arm_cns.targets) > 1:
                    t2 = arm_cns.targets[1]
                    arm_cns.targets.remove(t2)

    # Custom shapes
    #   root
    if root_pbone.custom_shape == None:
        root_pbone.custom_shape = bpy.data.objects.get("cs_torus_03")

    #   tip
    tip_pbone = get_pose_bone("c_"+name+"_tip" + side_arg)
    if tip_pbone.custom_shape == None:
        tip_pbone.custom_shape = bpy.data.objects.get("cs_torus_03")

    #   inters controllers
    if type == '2':
        for inter_i in spline_inters_data:
            idx = '%02d' % inter_i
            c_inter = get_pose_bone("c_"+name+"_inter_"+idx+side_arg)
            if c_inter.custom_shape == None:
                c_inter.custom_shape = bpy.data.objects.get("cs_sphere")
            set_custom_shape_scale(c_inter, 0.55)
            #c_master.custom_shape_transform = get_pose_bone("cp_spline_master_"+idx+side_arg)

    #   masters controllers
    if type == '2':
        for master_i in spline_masters_data:
            idx = '%02d' % master_i
            c_master = get_pose_bone("c_"+name+"_master_"+idx+side_arg)
            if c_master.custom_shape == None:
                c_master.custom_shape = get_object("cs_sphere")
            #c_master.custom_shape_transform = get_pose_bone("cp_spline_master_"+idx+side_arg)

    #   individual controllers
    for i in range(1, amount + 2):
        id = '%02d' % i
        c_bone_name = "c_" + name + "_" + id + side_arg
        c_bone = get_pose_bone(c_bone_name)
        if c_bone == None:            
            continue
        if c_bone.custom_shape == None:
            c_bone.custom_shape = get_object("cs_torus_04")

    #   curvy
    if type == '1':# only for type 1
        curvy_pbone = get_pose_bone(curvy_name)
        if curvy_pbone.custom_shape == None:
            curvy_pbone.custom_shape = get_object("cs_torus_03")

    # Bone group
    used_side = side_arg if renamed_side == None else renamed_side  
    group_color = bpy.context.active_object.pose.bone_groups.get('body'+used_side[-2:])
 

    #   c_master group
    master_group_color = None
    if type == '2':
        group_name = "Spline Masters"
        master_group_color = arp_armature.pose.bone_groups.get(group_name)
        if master_group_color == None:
            master_group_color = arp_armature.pose.bone_groups.new(name=group_name)
            master_group_color.color_set = "CUSTOM"
            color_red = (1.0, 0.0, 0.0)
            master_group_color.colors.normal = color_red

            for i, channel in enumerate(master_group_color.colors.select):
                master_group_color.colors.select[i] = color_red[i] + 0.4
            for i, channel in enumerate(master_group_color.colors.active):
                master_group_color.colors.active[i] = color_red[i] + 0.5

    if root_pbone.bone_group == None:
        root_pbone.bone_group = group_color
    if tip_pbone.bone_group == None:
        tip_pbone.bone_group = group_color
    if type == '1':# only for type 1
        if curvy_pbone.bone_group == None:
            curvy_pbone.bone_group = group_color

    for i in range(1, amount+2):
        id = '%02d' % (i)
        c_bone_name = "c_"+name+"_"+id+side_arg
        c_bone = get_pose_bone(c_bone_name)
        if c_bone:
            if c_bone.bone_group == None:
                c_bone.bone_group = group_color

        if type == "2":
            c_bone_master_name = "c_"+name+"_master_"+id+side_arg
            c_bone_master = get_pose_bone(c_bone_master_name)
            if c_bone_master:
                if c_bone_master.bone_group == None:
                    c_bone_master.bone_group = master_group_color

            c_bone_inter_name = "c_"+name+"_inter_"+id+side_arg
            c_bone_inter = get_pose_bone(c_bone_inter_name)
            if c_bone_inter:
                if c_bone_inter.bone_group == None:
                    c_bone_inter.bone_group = group_color

    arp_armature.data.pose_position = "POSE"

    # Restore the pose
    bpy.ops.object.mode_set(mode='POSE')
    restore_pose(controllers_saved_transforms)

    # Rotation mode to euler
    if not scn.arp_retro_splineik_quat:
        for c_name in controllers_list:
            pbone = get_pose_bone(c_name)
            pbone.rotation_mode = "XYZ"


    # --Edit Mode--
    bpy.ops.object.mode_set(mode='EDIT')

    # select the ref bones only
    bpy.ops.armature.select_all(action='DESELECT')
    for iname in ref_bones_dict:
        select_edit_bone(iname)

    # Rename base name and side
    if renamed_side or new_name != name:
        if renamed_side == None:
            renamed_side = side_arg

        for bname in spline_ik_bones:
            b = get_edit_bone(bname)
            b.name = b.name.replace(side_arg, renamed_side)# side
            b.name = b.name.replace(name, new_name)# base name

        # force drivers to refresh because of bones name change, there are udpate issues otherwise
        # switch mode as a hack to force drivers update dependencies
        bpy.ops.object.mode_set(mode='OBJECT')
        bpy.ops.object.mode_set(mode='EDIT')
        tip_id = '%02d' % (amount)
        tip_bone_name = new_name + "_" + tip_id + renamed_side

        # neutral change to the expression to force the update
        dps = []
        for prop in ['xz_scale_mode', 'bulge', 'y_scale_mode']:
            dps.append(['pose.bones["' + tip_bone_name + '"].constraints["Spline IK"].'+prop, -1])

        for i in range(1, amount+1):
            idx = '%02d' % i
            bname = new_name+"_"+idx+renamed_side
            dps.append(['pose.bones["'+bname+'"].rotation_euler', 1])
            #print("append DP", 'pose.bones["'+bname+'"].rotation_euler')
        for dp in dps:
            dp_str, dp_idx = dp[0], dp[1]
            dr = bpy.context.active_object.animation_data.drivers.find(dp_str, index=dp_idx)
            if dr == None:
                continue

            dr.driver.expression += ' '
            dr.driver.expression = dr.driver.expression[:-1]

        # curve
        nurbs.name = nurbs.name.replace(side_arg, renamed_side)

    # restore X mirror
    bpy.context.object.data.use_mirror_x = xmirror_state

    # restore layers
    restore_armature_layers(layers_select)

    print("Spline IK set.")

    # end set_spline_ik()


def align_spline_ik_bones(name, side):
    spline_01_ref = get_edit_bone(name + "_01_ref" + side)

    spline_count = spline_01_ref["spline_count"]

    type = '1'
    if "spline_type" in spline_01_ref.keys():#backward-compatibility
        type = spline_01_ref["spline_type"]

    cont_freq = 1
    if "spline_cont_freq" in spline_01_ref.keys():
        cont_freq = spline_01_ref["spline_cont_freq"]

    interpolation = 'LINEAR'
    if "spline_interpolation" in spline_01_ref.keys():
        interpolation = spline_01_ref["spline_interpolation"]

    spline_parent_last = spline_01_ref["spline_parent_last"]
    if spline_parent_last != "none":
        spline_parent_last = spline_parent_last.replace("spline", name)

    spline_parent_master = "stretch"
    if "spline_parent_master" in spline_01_ref.keys():
        spline_parent_master = spline_01_ref["spline_parent_master"]

        if spline_parent_master != "none":
            spline_parent_master = spline_parent_master.replace("spline", name)

    spline_parent_last_master = "c_spline_root"
    if "spline_parent_last_master" in spline_01_ref.keys():
        spline_parent_last_master = spline_01_ref["spline_parent_last_master"]
        if spline_parent_last_master != "none":
            spline_parent_last_master = spline_parent_last_master.replace("spline", name)

    spline_masters_data = None
    if "spline_masters_data" in spline_01_ref.keys():
        spline_masters_data = dict_to_int(spline_01_ref["spline_masters_data"])
    spline_inters_data = None
    if "spline_inters_data" in spline_01_ref.keys():
        spline_inters_data = dict_to_int(spline_01_ref["spline_inters_data"])

    # get ref bones
    ref_bones = []
    for i in range(1, spline_count + 1):
        id = '%02d' % i
        ref_bone = get_edit_bone(name + "_" + id + "_ref" + side)
        ref_bones.append(ref_bone)

    first_ref_bone = ref_bones[0]
    last_ref_bone = ref_bones[len(ref_bones) - 1]
    root_parent = None

    # Controller -Root
    root_bone_name = "c_" + name + "_root" + side    
    root_bone = get_edit_bone(root_bone_name)
    
    #   transforms   
    root_bone.head = first_ref_bone.head.copy()
    root_bone.tail = last_ref_bone.tail.copy()
    root_bone.tail = root_bone.head + ((root_bone.tail - root_bone.head) / 3)
    root_bone.roll = first_ref_bone.roll

    #   parent
    if first_ref_bone.parent:
        is_a_ref_bone = False
        if first_ref_bone.parent.name[:-2][-4:] == "_ref" or "_ref_dupli_" in first_ref_bone.parent.name:
            is_a_ref_bone = True
        if is_a_ref_bone:

            if '_ref_dupli' in first_ref_bone.parent.name:
                bname = 'c_' + first_ref_bone.parent.name.replace('_ref_dupli', '_dupli')
            else:
                bname = 'c_' + first_ref_bone.parent.name.replace('_ref.', '.')
            root_parent = get_edit_bone(bname)
            # in case of c_root.x, use c_root_master.x instead since it's more useful this way
            if root_parent.name == "c_root.x":
                print("parent is c_root.x, parent to c_root_master.x instead")
                root_parent = get_edit_bone("c_root_master.x")
        else:
            root_parent = first_ref_bone.parent
    else:
        root_parent = get_edit_bone(get_first_master_controller())

    root_bone.parent = root_parent

    # Spline IK chain
    for i, ref_bone in enumerate(ref_bones):
        ik_chain_name = ref_bone.name.replace("_ref", "")
        ik_chain = get_edit_bone(ik_chain_name)
        copy_bone_transforms(ref_bone, ik_chain)
        if i == 0:
            ik_chain.parent = root_bone
        else:
            prev_ik_name = ref_bones[i - 1].name.replace("_ref", "")
            prev_ik = get_edit_bone(prev_ik_name)
            ik_chain.parent = prev_ik

    # Stretch bone
    stretch_bone_name = name + "_stretch" + side
    stretch_bone = get_edit_bone(stretch_bone_name)
    stretch_bone.head = first_ref_bone.head.copy()
    stretch_bone.tail = last_ref_bone.tail.copy()
    stretch_bone.roll = first_ref_bone.roll

    tip_name = "c_" + name + "_tip" + side
    c_tip = get_edit_bone(tip_name)

    # Controllers -masters
    if type == '2':
        for master_i in spline_masters_data:
            master_id = '%02d' % master_i
            master_name = 'c_'+name+'_master_'+master_id+side
            master_bone = get_edit_bone(master_name)
            ref_id = '%02d' % spline_masters_data[master_i]
            ref_id_prev = '%02d' % (spline_masters_data[master_i]-1)
            ref_bone_name = name+"_"+ref_id+"_ref"+side
            ref_bone = get_edit_bone(ref_bone_name)

            if master_i == len(spline_masters_data):# tip
                ref_bone = get_edit_bone(name+"_"+ref_id_prev+"_ref"+side)

            # coords
            copy_bone_transforms(ref_bone, master_bone)
            if master_i == len(spline_masters_data):# tip
                master_bone.tail = master_bone.tail + (master_bone.tail-master_bone.head)
                master_bone.head = ref_bone.tail.copy()

            # parent
            if master_i == 1: #root
                master_bone.parent = root_bone
            elif master_i == len(spline_masters_data):# tip
                if spline_parent_last != "none":
                    parent_name = spline_parent_last+side
                    master_bone.parent = get_edit_bone(parent_name)
            else:# others
                if spline_parent_master == "stretch":
                    master_bone.parent = stretch_bone

    # Controllers -inters
    if type == '2':
        for inter_i in spline_inters_data:
            inter_id = '%02d' % inter_i
            inter_id_prev = '%02d' % (inter_i-1)
            inter_name = 'c_'+name+'_inter_'+inter_id+side
            inter_bone = get_edit_bone(inter_name)
            ref_bone_name = name+"_"+inter_id+"_ref"+side
            ref_bone = get_edit_bone(ref_bone_name)
            if inter_i == len(spline_inters_data):# tip
                ref_bone = get_edit_bone(name+"_"+inter_id_prev+"_ref"+side)

            # coords
            copy_bone_transforms(ref_bone, inter_bone)
            if inter_i == len(spline_inters_data):# tip
                inter_bone.tail = inter_bone.tail + (inter_bone.tail-inter_bone.head)
                inter_bone.head = ref_bone.tail.copy()

    # Controllers -individuals
    for i in range(1, spline_count+1):
        id = '%02d' % i
        c_bone_name = 'c_'+name+'_'+id+side
        ref_bone_name = name+"_"+id+"_ref"+side
        c_bone = get_edit_bone(c_bone_name)
        ref_bone = get_edit_bone(ref_bone_name)
        # coords
        copy_bone_transforms(ref_bone, c_bone)
        # parent
        if type == '1':
            if i == 1:
                c_bone.parent = root_bone
            else:
                c_bone.parent = stretch_bone
        elif type == '2':
            spline_b_name = name+'_'+id+side
            spline_b = get_edit_bone(spline_b_name)
            c_bone.parent = spline_b

    # Controller -tip
    if type == '1':# not used in the second type
        tip_id = '%02d' % (spline_count+1)
        tip_bone = get_edit_bone("c_"+name+"_"+tip_id+side)
        ref_id = '%02d' % (spline_count)
        ref_bone_name = name+"_"+ref_id+"_ref"+side
        ref_bone = get_edit_bone(ref_bone_name)
            # coords
        copy_bone_transforms(ref_bone, tip_bone)
        tip_bone.tail = tip_bone.tail + (ref_bone.tail - ref_bone.head)
        tip_bone.head = ref_bone.tail.copy()
        tip_bone.roll = ref_bone.roll
            # parent
        if spline_parent_last != "none":
            parent_name = spline_parent_last + side
            tip_bone.parent = get_edit_bone(parent_name)


    # Controller -curvy
    if type == '1':# only for type 1
        curvy_name = "c_"+name+"_curvy" + side
        c_curvy = get_edit_bone(curvy_name)
            # get the mid bone
        mid_id = '%02d' % (int((spline_count + 1) / 2) + 1)
        mid_bone = get_edit_bone(name+"_" + mid_id + "_ref" + side)
        c_curvy.head = mid_bone.head.copy()
        c_curvy.tail = mid_bone.tail.copy()
        length = (last_ref_bone.tail - first_ref_bone.head).magnitude * 0.33
        c_curvy.tail = c_curvy.head + ((c_curvy.tail - c_curvy.head).normalized() * length)
        c_curvy.roll = first_ref_bone.roll
            # relation
        c_curvy.parent = stretch_bone

    # Controller -tip master
        # get the mid bone
    last_id = '%02d' % (int(spline_count))
    last_bone = get_edit_bone(name+"_" + last_id + "_ref" + side)
        # coords
    c_tip.head = last_bone.tail.copy()
    c_tip.tail = c_tip.head + (last_bone.tail - last_bone.head) * 1.5
    c_tip.roll = last_bone.roll
        # parent
    if spline_parent_last_master != "none":
        c_tip.parent = get_edit_bone(spline_parent_last_master+side)


def create_spline_nurbs(_amount=4, _arp_armature=None, _side_arg=None, _smoothness=4):
    #rig_id = _arp_armature['rig_id']
    nurbs_name = "spline_ik_curve" + _side_arg
    
    nurbs = ard.get_spline_ik(_arp_armature, _side_arg)
    
    # delete the current nurbs if any
    if nurbs:
        delete_object(nurbs)
    
    # add
    bpy.ops.curve.primitive_nurbs_curve_add(radius=1, enter_editmode=False, location=_arp_armature.location)
    bpy.context.active_object.name = nurbs_name
    nurbs_name = bpy.context.active_object.name# may be duplicate .001, .002 after renaming
    nurbs = get_object(nurbs_name)
    
    # parent
    #nurbs.parent = _arp_armature
    parent_objects([nurbs], _arp_armature, mesh_only=False)
    
    # set vertices
    #   remove all    
    current_spline = nurbs.data.splines[0]
    nurbs.data.splines.remove(current_spline)
    # add
    new_spline = nurbs.data.splines.new('NURBS')
    new_spline.points.add(_amount)
    new_spline.use_endpoint_u = True
    new_spline.order_u = _smoothness

    # set collection
    rig_collec = get_rig_collection(_arp_armature)
    #   remove from root collection
    if bpy.context.scene.collection.objects.get(nurbs.name):
        bpy.context.scene.collection.objects.unlink(nurbs)
    #   remove from other collections
    for other_collec in nurbs.users_collection:
        other_collec.objects.unlink(nurbs)
    #   assign to collection
    rig_collec.objects.link(nurbs)
    
    '''
    if len(_arp_armature.users_collection):
        for collec in _arp_armature.users_collection:
            if len(collec.name.split('_')) == 1:
                continue
            if collec.name.split('_')[1] == "rig" or collec.name.endswith("grp_rig"):
                # remove from root collection
                if bpy.context.scene.collection.objects.get(nurbs.name):
                    bpy.context.scene.collection.objects.unlink(nurbs)
                # remove from other collections
                for other_collec in nurbs.users_collection:
                    other_collec.objects.unlink(nurbs)
                # assign to collection
                collec.objects.link(nurbs)
                print("ADD TO RIG COLLECT", collec.name)
    '''

    set_active_object(nurbs_name)

    return nurbs


def align_spline_curve(spline, ref_bones_dict):
    
    for i, ref_bone_name in enumerate(ref_bones_dict):
        ref_bone = ref_bones_dict[ref_bone_name]
        x, y, z = ref_bone[0][0], ref_bone[0][1], ref_bone[0][2]
        spline.points[i].co = (x, y, z, 1)

        # last point
        if i == len(ref_bones_dict) - 1:
            x, y, z = ref_bone[1][0], ref_bone[1][1], ref_bone[1][2]
            spline.points[i + 1].co = (x, y, z, 1)


def set_spline_hooks(spline=None, armature=None, length=None, type="1", spline_masters_data=None, spline_inters_data=None, freq=None, interpolation='LINEAR', side=None, name="spline"):
    # Delete existing modifiers
    for mod in spline.modifiers:
        spline.modifiers.remove(mod)

    # Add hooks
    if type == "1":
        cont_name = "c_"+name+"_"

        for i in range(0, length + 1):
            id = '%02d' % (i+1)
            new_mod = spline.modifiers.new(type="HOOK", name="Hook_"+str(i+1))
            new_mod.object = armature
            new_mod.subtarget = "c_"+name+"_"+id+side
            new_mod.vertex_indices_set([i])

    elif type == "2":
        cont_name = "c_"+name+"_master_"
        inter_name = "c_"+name+"_inter_"
        master_i = 1
        last_master_i = 1

        for i in range(0, length + 1):
            inter_i = i+1
            inter_id = '%02d' % (inter_i)
            new_mod = spline.modifiers.new(type="HOOK", name="Hook_"+str(i+1)+"inter_"+inter_id)
            new_mod.object = armature
            new_mod.subtarget = inter_name+inter_id+side
            new_mod.vertex_indices_set([i])


def get_bbones_name(side):
    # returns the bbones name for the current side
    name = "bbones"# default name, backward-compatibility

    for b in bpy.context.active_object.data.bones:
        if b.keys():
            bside = get_bone_side(b.name)
            if bside == side:
                if "arp_bbones" in b.keys() and "_ref" in b.name:
                    name = b['arp_bbones']
                    break

    return name


def set_bendy_bones(amount, bbones_segment_args=5, scale=1.0, side_arg=None, new_side=None, new_name="bbones"):
    print("set bendy bones")

    # safety
    xmirror_state = bpy.context.object.data.use_mirror_x
    bpy.context.object.data.use_mirror_x = False
    bpy.context.scene.tool_settings.use_keyframe_insert_auto = False

    # enable all layers
    layers_select = enable_all_armature_layers()

    # get side
    if side_arg == None:
        side_arg = ".x"

    # get current name
    name = get_bbones_name(side_arg)
    """
    if bpy.context.mode == "EDIT_ARMATURE":
        if len(bpy.context.selected_editable_bones):
            sel_bone = bpy.context.selected_editable_bones[0]
            if sel_bone.keys():
                if "arp_bbones" in sel_bone.keys():
                    name = bpy.context.selected_editable_bones[0].name.split('_')[0]
    """
    # get the existing limbs
    limb_sides.get_multi_limbs()

    # define the newly set side if any, and set the renamed side
    renamed_side = None
    if new_side:
        if new_side != side_arg[-2:]:  # .l != .x
            dupli_id, found_base = get_next_dupli_id(new_side, 'bbones')
            if not found_base:
                renamed_side = new_side
            else:
                renamed_side = '_dupli_' + dupli_id + new_side

    # --Edit Mode--
    bpy.ops.object.mode_set(mode='EDIT')

    print("  set bones")

    # look for the existing bendy bones if any
    root_pos = Vector((0, 0, 0))
    root_roll = None
    tip_pos = Vector((0, 0, 1))
    root_bone = get_edit_bone(name + "_01_ref" + side_arg)
    tip_bone = None
    bbones_vec = Vector((0, 0, 1))

    if root_bone:
        root_pos = root_bone.head.copy()
        root_roll = root_bone.roll

        # look for the tip bone
        for i in range(1, 1024):
            id = '%02d' % i
            supposed_tip_bone = get_edit_bone(name + "_" + id + "_ref" + side_arg)
            if supposed_tip_bone:
                tip_bone = supposed_tip_bone

    if tip_bone:
        tip_pos = tip_bone.tail.copy()
        bbones_vec = tip_bone.tail - root_bone.head

    # Remove bones out of range
    for i in range(amount + 1, 1024):
        id = '%02d' % i

        # ref bones
        ref_name = name + "_" + id + "_ref" + side_arg
        ref_bone = get_edit_bone(ref_name)
        if ref_bone:
            delete_edit_bone(ref_bone)

        # bendy-bones
        bname = name + "_" + id + side_arg
        bbone = get_edit_bone(bname)
        if bbone:
            delete_edit_bone(bbone)

        # control bones
        cname = "c_" + name + "_" + id + side_arg
        control_bone = get_edit_bone(cname)
        if control_bone:
            delete_edit_bone(control_bone)

        # bones in
        bname = name + "_in_" + id + side_arg
        b = get_edit_bone(bname)
        if b:
            delete_edit_bone(b)

        # bones out
        bname = name + "_out_" + id + side_arg
        b = get_edit_bone(bname)
        if b:
            delete_edit_bone(b)

        # tip bone
    for i in range(1, 1024):
        if i != amount:
            id = '%02d' % i
            cname = "c_tip_" + name + "_" + id + side_arg
            tip_bone = get_edit_bone(cname)
            if tip_bone:
                print("deleting tip bone", cname)
                delete_edit_bone(tip_bone)

    # Create bones
    bone_length = bbones_vec.magnitude / amount
    ref_bones_dict = {}
    bbones_bones = []

    for i in range(1, amount + 1):
        id = '%02d' % i
        prev_id = '%02d' % (i - 1)

        # reference
        # names
        ref_bone_name = name + "_" + id + "_ref" + side_arg
        bbones_bones.append(ref_bone_name)
        ref_bone = get_edit_bone(ref_bone_name)
        if ref_bone == None:
            ref_bone = bpy.context.active_object.data.edit_bones.new(ref_bone_name)
            ref_bone["arp_duplicate"] = True
        ref_bone.use_deform = False
        # coords
        ref_bone.head = root_pos + ((bone_length * (i - 1)) * bbones_vec.normalized())
        ref_bone.tail = ref_bone.head + (bone_length * bbones_vec.normalized())

        if root_roll != None:
            ref_bone.roll = root_roll
        # save in a dict for later use
        ref_bones_dict[ref_bone_name] = ref_bone.head.copy(), ref_bone.tail.copy(), ref_bone.roll

        # relation
        if ref_bone.parent == None:
            if i == 1:
                ref_bone.parent = get_edit_bone(get_first_master_controller())
            else:
                ref_bone.parent = get_edit_bone(name + "_" + prev_id + "_ref" + side_arg)
                ref_bone.use_connect = True
        # layer
        set_bone_layer(ref_bone, 17)

        # store the params in the root bone properties
        if i == 1:
            ref_bone["bbones_count"] = amount
            ref_bone["bbones_segments"] = bbones_segment_args
            ref_bone["bbones_scale"] = scale
            ref_bone["bbones_name"] = new_name

        vec = (ref_bone.tail - ref_bone.head)
        dist = 0.93

        # bendy-bones
        bbone_name = name + "_" + id + side_arg
        bbone = get_edit_bone(bbone_name)
        if bbone == None:
            bbone = create_edit_bone(bbone_name)
            set_bone_layer(bbone, 8)
        bbone.head, bbone.tail = Vector((0, 0, 0)), Vector((0, 0, 1))        
        bbones_bones.append(bbone_name)

        # bones _in
        b_in_name = name + "_in_" + id + side_arg
        b_in = get_edit_bone(b_in_name)
        if b_in == None:
            b_in = create_edit_bone(b_in_name)
            set_bone_layer(b_in, 8)
        b_in.head, b_in.tail = Vector((0, 0, 0)), Vector((0, 0, 1))
        b_in.use_deform = False
        
        bbones_bones.append(b_in_name)

        # bone _out
        b_out_name = name + "_out_" + id + side_arg
        b_out = get_edit_bone(b_out_name)
        if b_out == None:
            b_out = create_edit_bone(b_out_name)
            set_bone_layer(b_out, 8)
        b_out.head, b_out.tail = Vector((0, 0, 0)), Vector((0, 0, 1))
        b_out.use_deform = False        
        bbones_bones.append(b_out_name)

        # control
        b_cont_name = "c_" + name + "_" + id + side_arg
        b_cont = get_edit_bone(b_cont_name)
        if b_cont == None:
            b_cont = create_edit_bone(b_cont_name)
            set_bone_layer(b_cont, 0)
        b_cont.head, b_cont.tail = Vector((0, 0, 0)), Vector((0, 0, 1))
        b_cont.use_deform = False
        
        bbones_bones.append(b_cont_name)

        # tip controller for the last bone
        if i == amount:
            tip_cont_name = "c_tip_" + name + "_" + id + side_arg
            btip_cont = get_edit_bone(tip_cont_name)
            if btip_cont == None:
                btip_cont = create_edit_bone(tip_cont_name)
                set_bone_layer(btip_cont, 0)
            btip_cont.head, btip_cont.tail = Vector((0, 0, 0)), Vector((0, 0, 1))
            btip_cont.use_deform = False
            
            bbones_bones.append(tip_cont_name)

        b_in.parent = get_edit_bone("c_" + name + "_" + id + side_arg)
        bbone.parent = b_in

        # bbones options
        bbone.bbone_segments = bbones_segment_args

    # parent _out
    for i in range(1, amount + 1):
        id = '%02d' % i
        next_id = '%02d' % (i + 1)

        b_out_name = name + "_out_" + id + side_arg
        b_out = get_edit_bone(b_out_name)

        if i == amount:
            tip_cont_name = "c_tip_" + name + "_" + id + side_arg
            btip_cont = get_edit_bone(tip_cont_name)
            b_out.parent = btip_cont
        else:
            b_out.parent = get_edit_bone("c_" + name + "_" + next_id + side_arg)

    # Align bones
    align_bendy_bones(name, side_arg)

    # Constraints
    bpy.ops.object.mode_set(mode='POSE')

    for i in range(1, amount + 1):
        id = '%02d' % i
        prev_id = '%02d' % (i - 1)
        next_id = '%02d' % (i + 1)
        b_name = name + "_" + id + side_arg
        pbone = get_pose_bone(b_name)

        # stretch to constraint
        cns = pbone.constraints.get("Stretch To")
        if cns == None:
            cns = pbone.constraints.new("STRETCH_TO")
            cns.name = "Stretch To"
        cns.target = bpy.context.active_object
        cns.subtarget = name + "_out_" + id + side_arg
        cns.volume = "NO_VOLUME"

        # bbones options
        b_in_name = name + "_in_" + id + side_arg
        b_out_name = name + "_out_" + id + side_arg
        pbone.bone.bbone_handle_type_start = "ABSOLUTE"
        pbone.bone.bbone_handle_type_end = "ABSOLUTE"
        pbone.bone.bbone_custom_handle_start = get_pose_bone(b_in_name).bone
        pbone.bone.bbone_custom_handle_end = get_pose_bone(b_out_name).bone

        # Custom shapes
        # controllers
    for i in range(1, amount + 1):
        id = '%02d' % (i)
        c_bone = get_pose_bone("c_" + name + "_" + id + side_arg)
        if c_bone.custom_shape == None:
            c_bone.custom_shape = bpy.data.objects.get("cs_torus_03")

        # tip controller
        if i == amount:
            c_tip = get_pose_bone("c_tip_" + name + "_" + id + side_arg)
            if c_tip.custom_shape == None:
                c_tip.custom_shape = bpy.data.objects.get("cs_torus_03")

    # Bone group
    used_side = side_arg if renamed_side == None else renamed_side
    # if used_side.endswith(".x"):
    group_color = bpy.context.active_object.pose.bone_groups.get('body.x')

    for i in range(1, amount + 1):
        id = '%02d' % (i)
        c_bone = get_pose_bone("c_" + name + "_" + id + side_arg)
        if c_bone.bone_group == None:
            c_bone.bone_group = group_color
        # tip controller
        if i == amount:
            c_tip = get_pose_bone("c_tip_" + name + "_" + id + side_arg)
            if c_tip.bone_group == None:
                c_tip.bone_group = group_color

    bpy.ops.object.mode_set(mode='EDIT')

    # select the ref bones only
    bpy.ops.armature.select_all(action='DESELECT')
    for iname in ref_bones_dict:
        select_edit_bone(iname)

    # Tag with a custom prop
    for bname in bbones_bones:
        get_edit_bone(bname)["arp_bbones"] = new_name

    # Rename side
    if renamed_side or name != new_name:
        if renamed_side == None:
            renamed_side = side_arg

        # bones
        for bname in bbones_bones:
            b = get_edit_bone(bname)
            b.name = b.name.replace(side_arg, renamed_side)# side
            b.name = b.name.replace(name, new_name)# base name

    # restore X mirror
    bpy.context.object.data.use_mirror_x = xmirror_state

    # restore layers
    restore_armature_layers(layers_select)

    print("Bendy Bones set.")

    # end set_bendy_bones()


def align_bendy_bones(name, side):
    first_ref_bone = get_edit_bone(name + "_01_ref" + side)

    bbones_count = first_ref_bone["bbones_count"]

    if not "bbones_scale" in first_ref_bone.keys():
        first_ref_bone["bbones_scale"] = 1.0
    scale = first_ref_bone["bbones_scale"]

    cont_scale = 0.4 * scale

    # get ref bones
    ref_bones = []
    for i in range(1, bbones_count + 1):
        id = '%02d' % i
        ref_bone = get_edit_bone(name + "_" + id + "_ref" + side)
        ref_bones.append(ref_bone)

    first_ref_bone = ref_bones[0]
    last_ref_bone = ref_bones[len(ref_bones) - 1]
    root_parent = None

    for i, ref_bone in enumerate(ref_bones):
        dist = 0.9
        vec = ref_bone.tail - ref_bone.head
        id = '%02d' % (i + 1)
        prev_id = '%02d' % (i)

        # align bbones
        bbone = get_edit_bone(name + "_" + id + side)
        #print(name + "_" + id + side)
        copy_bone_transforms(ref_bone, bbone)
        bbone.bbone_x = (bbone.tail - bbone.head).magnitude * 0.075
        bbone.bbone_z = (bbone.tail - bbone.head).magnitude * 0.075

        # align bones _in
        b_in = get_edit_bone(name + "_in_" + id + side)
        b_in.head = ref_bone.head + (1 - dist) * -vec
        b_in.tail = ref_bone.head.copy()
        b_in.roll = ref_bone.roll
        b_in.bbone_x = (b_in.tail - b_in.head).magnitude * 0.05
        b_in.bbone_z = (b_in.tail - b_in.head).magnitude * 0.05

        # align bones _out
        b_out = get_edit_bone(name + "_out_" + id + side)
        b_out.head = ref_bone.tail.copy()
        b_out.tail = ref_bone.tail + (vec * (1 - dist))
        b_out.roll = ref_bone.roll
        b_out.bbone_x = (b_out.tail - b_out.head).magnitude * 0.05
        b_out.bbone_z = (b_out.tail - b_out.head).magnitude * 0.05

        # align controllers
        b_cont = get_edit_bone("c_" + name + "_" + id + side)
        b_cont.head = ref_bone.head.copy()
        b_cont.tail = b_cont.head + (ref_bone.tail - ref_bone.head) * cont_scale
        b_cont.bbone_x = (b_cont.tail - b_cont.head).magnitude * 1
        b_cont.bbone_z = (b_cont.tail - b_cont.head).magnitude * 1

        # parent
        root_parent = None
        
        if ref_bone.parent:
            root_parent = parent_retarget(ref_bone)
            '''
            is_a_ref_bone = False
            if ref_bone.parent.name[:-2][-4:] == "_ref" or "_ref_dupli_" in ref_bone.parent.name:
                is_a_ref_bone = True
            if is_a_ref_bone:
                print(ref_bone.name, "parent is a ref bone")
                
                if '_ref_dupli' in ref_bone.parent.name:
                    bname = 'c_' + ref_bone.parent.name.replace('_ref_dupli', '_dupli')
                else:
                    bname = 'c_' + ref_bone.parent.name.replace('_ref.', '.')
                    
                print(" >", bname)
                
                root_parent = get_edit_bone(bname)

                # in case of c_root.x, use c_root_master.x instead since it's more useful this way
                if root_parent:
                    if root_parent.name == "c_root.x":
                        print("parent is c_root.x, parent to c_root_master.x instead")
                        root_parent = get_edit_bone("c_root_master.x")
            else:
                root_parent = ref_bone.parent
            '''
        else:
            root_parent = get_edit_bone(get_first_master_controller())

        b_cont.parent = root_parent

        # tip
        if i == len(ref_bones) - 1:
            btip_cont = get_edit_bone("c_tip_" + name + "_" + id + side)
            btip_cont.head = ref_bone.tail.copy()
            btip_cont.tail = btip_cont.head + (ref_bone.tail - ref_bone.head) * cont_scale
            btip_cont.bbone_x = (btip_cont.tail - btip_cont.head).magnitude * 1
            btip_cont.bbone_z = (btip_cont.tail - btip_cont.head).magnitude * 1

            # tip parent
            if btip_cont.parent == None:
                btip_cont.parent = b_cont
            """
            # first bone parent
            if ref_bone.parent == broot_cont:
                print(ref_bone.name, "is parented to the previous bone")
                prev_cont = get_edit_bone("c_"+name+"_"+prev_id+side)
                b_cont.parent = prev_cont
            """


def reset_transf(c_pbone):
    c_pbone.rotation_mode = "XYZ"
    c_pbone.location = c_pbone.rotation_euler = [0, 0, 0]
    c_pbone.scale = [1, 1, 1]


def reset_spline_stretch_ctrl(name, side_arg):
    # the stretch to constraint of the spline bone must be reset for correct binding
    # of hook modifiers
    stretch_bone_name = name + "_stretch" + side_arg
    stretch_bone = get_pose_bone(stretch_bone_name)
    cns = stretch_bone.constraints.get("Stretch To")
    if cns:
        cns.rest_length = 0.0
        
        
def set_tail(tail_count, master_at_root=True, side_arg=None, new_side='.x', update_transforms=True):
    context = bpy.context
    current_mode = context.mode
    active_bone = None
    rig = get_object(bpy.context.active_object.name)
    
    if current_mode == 'POSE':
        try:
            active_bone = context.active_object.data.bones.active.name
        except:
            pass
    if current_mode == 'EDIT_ARMATURE':
        try:
            active_bone = context.active_object.data.edit_bones.active.name
        except:
            pass

    bpy.ops.object.mode_set(mode='EDIT')

    # disable the proxy picker to avoid bugs
    proxy_picker_state = disable_proxy_picker()

    # Show all layers
    layers_select = enable_all_armature_layers()
   
    # get the bone side     
    side = '.x'
    if side_arg:
        side = side_arg
        print('setting tail side:', side)
    else:
        if len(get_selected_edit_bones()):
            b_name = get_selected_edit_bones()[0].name
            side = get_bone_side(b_name)
        else:
            print("No bone selected")        
    
    root_ref_name = ard.spine_ref_dict['root']#"root_ref.x"
    root_name = ard.spine_bones_dict['root']#"root.x"
    c_root_master_name = ard.spine_bones_dict['c_root_master']
    c_root_name = ard.spine_bones_dict['c_root']
    
    
    # Get the last existing tail bone position to position bones later
    last_existing_tail = None
    for i in range(0, 32):
        tail_ref_name = "tail_" + '%02d' % i + '_ref'+side
        tail_ref = get_edit_bone(tail_ref_name)
        if tail_ref:
            last_existing_tail = tail_ref.tail.copy()
    
    print('last_existing_tail', last_existing_tail)            
    
    tail_bones_list = []
    created_tail_bones = []
    
    root = get_edit_bone(get_first_master_controller())        
    if get_edit_bone(c_root_master_name):
        root = get_edit_bone(c_root_master_name)

    # If the tail_00_ref bone does not exist, create it first
    first_tail_ref_name = "tail_00_ref"+side
    first_tail_ref = get_edit_bone(first_tail_ref_name)
    
    if first_tail_ref == None:
        print(first_tail_ref_name, 'not found, create it')
        first_tail_ref = rig.data.edit_bones.new(first_tail_ref_name)
        first_tail_ref.use_deform = False
        first_tail_ref.head = root.head + (-root.z_axis.normalized() * (root.tail - root.head).magnitude)
        first_tail_ref.tail = first_tail_ref.head + (-root.z_axis.normalized() * (root.tail - root.head).magnitude * 4)
        first_tail_ref.use_deform = False
        
        root_ref = get_edit_bone(root_ref_name)
        if root_ref:
            first_tail_ref.parent = root_ref
            
        created_tail_bones.append(first_tail_ref_name)
            
    # update bone transforms if the count has changed
    if 'tail_count' in first_tail_ref.keys():
        if tail_count != first_tail_ref['tail_count']:
            update_transforms = True
        
    # save tails setting in props
    first_tail_ref["master_at_root"] = master_at_root
    first_tail_ref["tail_count"] = tail_count
    first_tail_ref["tail_side"] = new_side
    first_tail_ref["tail_update_transforms"] = update_transforms
    
    # If the c_tail_00 bone does not exist, create it first    
    c_tail_0_name = 'c_tail_00'+side
    
    if get_edit_bone(c_tail_0_name) == None:    
        new_tail_cont = create_edit_bone(c_tail_0_name)        
        new_tail_cont.head = root.head + (-root.z_axis.normalized() * (root.tail - root.head).magnitude)
        new_tail_cont.tail = new_tail_cont.head + (-root.z_axis.normalized() * (root.tail - root.head).magnitude * 4)
        new_tail_cont.use_deform = True
        if get_edit_bone(root_name):
            new_tail_cont.parent = get_edit_bone(root_name)
            
        if not c_tail_0_name in created_tail_bones:
            created_tail_bones.append(c_tail_0_name)
            

    # Build the tail chain
    tail_vec = first_tail_ref.tail - first_tail_ref.head

    if last_existing_tail:
        tail_vec = last_existing_tail - first_tail_ref.head
        print("Found last tail bone:", last_existing_tail)

    tail_origin = first_tail_ref.head.copy()
    tail_parent = first_tail_ref.parent
    tail_rig_parent = get_edit_bone(c_tail_0_name).parent

    for i in range(0, tail_count):
        tail_id = '%02d' % i
        tail_id_prev = '%02d' % (i - 1)
        tail_ref = None
        new_tail_rig = None

        # reference bones
        tail_ref_name = "tail_" + tail_id + "_ref"+side
        tail_ref = get_edit_bone(tail_ref_name)
        if tail_ref == None:
            tail_ref = create_edit_bone(tail_ref_name) 
            
            if not tail_ref_name in created_tail_bones:
                created_tail_bones.append(tail_ref_name)

        tail_ref.use_deform = False
        tail_ref["arp_duplicate"] = True
        
        # controller bones
        c_tail_name = "c_tail_" + tail_id + side
        tail_control = get_edit_bone(c_tail_name)
        if tail_control == None:
            tail_control = create_edit_bone(c_tail_name, deform=True)
            tail_control.head, tail_control.tail = [0,0,0], [1,1,1]# temp coords
            
            if not c_tail_name in created_tail_bones:
                created_tail_bones.append(c_tail_name)
        else:           
            tail_control.use_deform = True

        tail_bones_list.append(tail_ref_name)
        tail_bones_list.append(c_tail_name)

        # position the tail ref
        if update_transforms:
            tail_ref.head = tail_origin + (tail_vec * (i)) / tail_count
            tail_ref.tail = tail_ref.head + (tail_vec / tail_count)
            tail_ref.roll = first_tail_ref.roll
       
        # parent the tails
        if tail_ref.parent == None:
            tail_ref.parent = get_edit_bone("tail_" + tail_id_prev + "_ref" + side)
            tail_ref.use_connect = True
        if tail_control.parent == None:
            tail_control.parent = get_edit_bone("c_tail_" + tail_id_prev + side)

    # Delete out of range tail bones
    for i in range(tail_count, 32):
        # tail bones names (controller, ref, proxy)
        tail_names = ["c_tail_" + '%02d' % i + side, "tail_" + '%02d' % i + '_ref' + side,
                      "c_tail_" + '%02d' % i + '_proxy' + side]
        for n in tail_names:
            t_bone = get_edit_bone(n)
            if t_bone:
                rig.data.edit_bones.remove(t_bone)
                
              
    # Master tail controller
    c_tail_master_name = "c_tail_master"+side
    c_tail_master = get_edit_bone(c_tail_master_name)

    # if does not exist, create it if more than 1 tail bone
    if tail_count > 1:
        created_master = False
        if c_tail_master == None:
            c_tail_master = create_edit_bone(c_tail_master_name)
            c_tail_master.head, c_tail_master.tail = [0,0,0], [0,0,1]# temp coords
            created_master = True
            
            if not c_tail_master_name in created_tail_bones:
                created_tail_bones.append(c_tail_master_name)
            
        c_tail_master.parent = tail_parent
        c_tail_master.use_deform = False
        tail_bones_list.append(c_tail_master_name)
    else:
        # if 1 tail bone only, no master needed
        if c_tail_master:
            delete_edit_bone(c_tail_master)

    # Set display parameters
    bpy.ops.object.mode_set(mode='POSE')

    for bname in tail_bones_list:
        pbone = get_pose_bone(bname)

        # bone group
        root_pbone = get_pose_bone(get_first_master_controller())
        if get_pose_bone(c_root_name):
            root_pbone = get_pose_bone(c_root_name)
        pbone.bone_group = root_pbone.bone_group

        # custom shape
        if not "_ref.x" in bname:
            if not "tail_master" in bname:
                if bpy.data.objects.get("cs_torus_03") == None:
                    append_from_arp(nodes=["cs_square"], type="object")

                pbone.custom_shape = get_object("cs_torus_03")
                get_data_bone(pbone.name).show_wire = True

            if "tail_master" in bname:
                if bpy.data.objects.get("cs_square") == None:
                    append_from_arp(nodes=["cs_square"], type="object")

                pbone.custom_shape = get_object("cs_square")
                get_data_bone(pbone.name).show_wire = True

        
        if not bname.endswith("_ref"+side):
            # Set layers
            # deforming controller bones, master controller
            set_bone_layer(get_data_bone(bname), 0)
            
            # set rotation mode
            if bname in created_tail_bones:
                pbone.rotation_mode = 'XYZ'
            
        else:
            # Set layers
            # reference bones
            set_bone_layer(get_data_bone(bname), 17)            

    # Create tail master constraints
    for bname in tail_bones_list:
        pbone = get_pose_bone(bname)
        if not "_ref.x" in bname and not "c_tail_master" in bname:
            cns = None
            if pbone.constraints.get("tail_master_rot") == None:
                cns = pbone.constraints.new('COPY_ROTATION')
            else:
                cns = pbone.constraints["tail_master_rot"]

            cns.target = bpy.context.active_object
            cns.subtarget = "c_tail_master"+side
            cns.target_space = cns.owner_space = 'LOCAL'
            cns.use_offset = True
            cns.name = "tail_master_rot"

    if tail_count > 1:
        c_tail_master_pb = get_pose_bone(c_tail_master_name)
        
        for i in range(0,3):
            c_tail_master_pb.lock_location[i] = True
        
        if c_tail_master_pb:
            if master_at_root == False:
                c_tail_master_pb.custom_shape_transform = get_pose_bone(c_tail_0_name)

            set_custom_shape_scale(c_tail_master_pb, 0.4)
            
    
    # select ref bones
    bpy.ops.object.mode_set(mode='EDIT')
    
    bpy.ops.armature.select_all(action='DESELECT')
    
    for iname in tail_bones_list:
        eb = get_edit_bone(iname)
        if eb.layers[17]:# ref bones only
            select_edit_bone(iname)
            
            
    # rename with new side
    for bname in tail_bones_list:
        eb = get_edit_bone(bname)
        eb.name = retarget_bone_side(eb.name, side[:-2]+new_side)

    # restore saved mode
    restore_current_mode(current_mode)

    # Restore layers
    restore_armature_layers(layers_select)

    # restore picker
    restore_proxy_picker(proxy_picker_state)

    # end set_tail()    


def set_leg_ikpole_distance(dist):
    context = bpy.context
    active_bone = context.active_object.data.edit_bones.active.name
    rig_name = bpy.context.active_object.name

    # get the bone side
    side = ""
    if len(get_selected_edit_bones()) > 0:
        b_name = get_selected_edit_bones()[0].name
        side = get_bone_side(b_name)
    else:
        print("No bone selected")

    foot_ref = get_edit_bone("foot_ref" + side)
    foot_ref["ik_pole_distance"] = dist
    
    
def set_leg_auto_ik_roll(auto_ik_roll):
    # get the bone side
    side = ""
    if len(get_selected_edit_bones()):
        b_name = get_selected_edit_bones()[0].name
        side = get_bone_side(b_name)
    else:
        print("No bone selected")
        return
        
    thigh_ref_name = ard.leg_ref_bones_dict['thigh']+side
    thigh_ref = get_edit_bone(thigh_ref_name)
    
    # store as prop    
    prop_name = ard.leg_props['auto_ik_roll']
    thigh_ref[prop_name] = auto_ik_roll


def set_leg_roll_cursor_distance(dist, fac):
    context = bpy.context
    # get the bone side
    side = ""
    if len(get_selected_edit_bones()):
        b_name = get_selected_edit_bones()[0].name
        side = get_bone_side(b_name)
    else:
        print("No bone selected")

    foot_ref = get_edit_bone("foot_ref" + side)
    foot_ref["roll_cursor_distance"] = dist
    foot_ref["roll_cursor_fac"] = fac


def set_arm_ikpole_distance(dist):
    context = bpy.context    
    rig_name = bpy.context.active_object.name

    # get the bone side
    side = ""
    if len(get_selected_edit_bones()):
        b_name = get_selected_edit_bones()[0].name
        side = get_bone_side(b_name)
    else:
        print("No bone selected")

    hand_ref = get_edit_bone("hand_ref" + side)
    hand_ref["ik_pole_distance"] = dist
    

def set_arm_auto_ik_roll(auto_ik_roll):
    # get the bone side
    side = ""
    if len(get_selected_edit_bones()):
        b_name = get_selected_edit_bones()[0].name
        side = get_bone_side(b_name)
    else:
        print("No bone selected")
        return
        
    arm_ref_name = ard.arm_ref_dict['arm']+side
    arm_ref = get_edit_bone(arm_ref_name)
    
    # store as prop    
    prop_name = ard.arm_props['auto_ik_roll']
    arm_ref[prop_name] = auto_ik_roll
    
    
    
def set_arm_softik(softik_enabled):
    # get the bone side
    side = ""
    if len(get_selected_edit_bones()):
        b_name = get_selected_edit_bones()[0].name
        side = get_bone_side(b_name)
    else:
        print("No bone selected")
        return
        
    arm_ref_name = ard.arm_ref_dict['arm']+side
    arm_ref = get_edit_bone(arm_ref_name)
    
    # store as prop    
    prop_name = ard.arm_props['soft_ik']
    arm_ref[prop_name] = softik_enabled
    
    
    bpy.ops.object.mode_set(mode='POSE')
    
    arm_ik_nostr_name = ard.arm_bones_dict['arm']['ik_nostr']+side
    arm_ik_nostr = get_pose_bone(arm_ik_nostr_name)
    forearm_ik_nostr_name = ard.arm_bones_dict['forearm']['ik_nostr']+side
    forearm_ik_nostr = get_pose_bone(forearm_ik_nostr_name)    
    
    # set IK constraint stretch setting
    cns_ik = forearm_ik_nostr.constraints.get('IK')
        
    if cns_ik:
        cns_ik.use_stretch = softik_enabled
    
    # set ik_stretch value (unused if constraints stretch is disabled)
    arm_ik_nostr.ik_stretch = 0.05
    forearm_ik_nostr.ik_stretch = 0.05    
    
    arm_ik_name = ard.arm_bones_dict['arm']['ik']+side
    arm_ik = get_pose_bone(arm_ik_name)
    forearm_ik_name = ard.arm_bones_dict['forearm']['ik']+side
    forearm_ik = get_pose_bone(forearm_ik_name)
    c_hand_ik_name = ard.arm_bones_dict['hand']['control_ik']+side
    c_hand_ik = get_pose_bone(c_hand_ik_name)
    cns_limit_name = 'LimitScale_SoftIK'
    
    rig = bpy.context.active_object
    
    dr_dps = [
        'pose.bones["'+arm_ik_name+'"].constraints["'+cns_limit_name+'"].max_x',
        'pose.bones["'+arm_ik_name+'"].constraints["'+cns_limit_name+'"].max_y',
        'pose.bones["'+arm_ik_name+'"].constraints["'+cns_limit_name+'"].max_z'
        ]
    
    if softik_enabled:
        # arm cns
        cns = arm_ik.constraints.new('LIMIT_SCALE')
        cns.name = cns_limit_name
        cns.owner_space = 'LOCAL'
        #   add drivers
        dr_dp = 'pose.bones["'+arm_ik_name+'"].constraints["'+cns.name+'"].max_x'
        tar_dp = 'pose.bones["c_hand_ik'+side+'"]["stretch_length"]'
        for dr_dp in dr_dps:        
            add_driver_to_prop(rig, dr_dp, tar_dp, array_idx=-1, exp="var")      
        
        # forearm cns
        cns = forearm_ik.constraints.new('LIMIT_SCALE')
        cns.name = cns_limit_name       
        cns.max_x = 1.0
        cns.max_y = 1.0
        cns.max_z = 1.0
        cns.owner_space = 'LOCAL'
        
        # tag pose bone as soft ik        
        c_hand_ik[prop_name] = 1
        
    else:        
        # remove arm cns driver
        for dr_dp in dr_dps:
            dr = rig.animation_data.drivers.find(dr_dp)
            if dr:
                rig.data.driver_remove(dr_dp, -1)
        
        # remove arm cns
        cns = arm_ik.constraints.get(cns_limit_name)
        if cns:
            arm_ik.constraints.remove(cns)
        
        # remove forearm cns
        cns = forearm_ik.constraints.get(cns_limit_name)
        if cns:
            forearm_ik.constraints.remove(cns)
            
        # untag pose bone as soft ik        
        if prop_name in c_hand_ik.keys():
            del c_hand_ik[prop_name]
            
    
    bpy.ops.object.mode_set(mode='EDIT')
    
    
def set_leg_softik(softik_enabled):
    # get the bone side
    side = ""
    if len(get_selected_edit_bones()):
        b_name = get_selected_edit_bones()[0].name
        side = get_bone_side(b_name)
    else:
        print("No bone selected")
        return
        
    thigh_ref_name = ard.leg_ref_bones_dict['thigh']+side
    thigh_ref = get_edit_bone(thigh_ref_name)
    
    # store as prop    
    prop_name = ard.leg_props['soft_ik']
    thigh_ref[prop_name] = softik_enabled
    
    
    bpy.ops.object.mode_set(mode='POSE')
    
    thigh_ik_nostr_name = ard.leg_bones_dict['thigh']['ik_nostr']+side
    thigh_ik_nostr = get_pose_bone(thigh_ik_nostr_name)
    leg_ik_nostr_name = ard.leg_bones_dict['calf']['ik_nostr']+side
    leg_ik_nostr = get_pose_bone(leg_ik_nostr_name)    
    
    # set IK constraint stretch setting
    cns_ik = leg_ik_nostr.constraints.get('IK')
        
    if cns_ik:
        cns_ik.use_stretch = softik_enabled
    
    # set ik_stretch value (unused if constraints stretch is disabled)
    thigh_ik_nostr.ik_stretch = 0.05
    leg_ik_nostr.ik_stretch = 0.05    
    
    thigh_ik_name = ard.leg_bones_dict['thigh']['ik']+side
    thigh_ik = get_pose_bone(thigh_ik_name)
    leg_ik_name = ard.leg_bones_dict['calf']['ik']+side
    leg_ik = get_pose_bone(leg_ik_name)
    c_foot_ik_name = ard.leg_bones_dict['foot']['control_ik']+side
    c_foot_ik = get_pose_bone(c_foot_ik_name)
    cns_limit_name = 'LimitScale_SoftIK'
    
    rig = bpy.context.active_object
    
    dr_dps = [
        'pose.bones["'+thigh_ik_name+'"].constraints["'+cns_limit_name+'"].max_x',
        'pose.bones["'+thigh_ik_name+'"].constraints["'+cns_limit_name+'"].max_y',
        'pose.bones["'+thigh_ik_name+'"].constraints["'+cns_limit_name+'"].max_z'
        ]
    
    if softik_enabled:
        # thigh cns
        cns = thigh_ik.constraints.new('LIMIT_SCALE')
        cns.name = cns_limit_name
        cns.owner_space = 'LOCAL'
        #   add drivers
        dr_dp = 'pose.bones["'+thigh_ik_name+'"].constraints["'+cns.name+'"].max_x'
        tar_dp = 'pose.bones["c_foot_ik'+side+'"]["stretch_length"]'
        for dr_dp in dr_dps:        
            add_driver_to_prop(rig, dr_dp, tar_dp, array_idx=-1, exp="var")      
        
        # leg cns
        cns = leg_ik.constraints.new('LIMIT_SCALE')
        cns.name = cns_limit_name       
        cns.max_x = 1.0
        cns.max_y = 1.0
        cns.max_z = 1.0
        cns.owner_space = 'LOCAL'
        
        # tag pose bone as soft ik        
        c_foot_ik[prop_name] = 1
        
    else:        
        # remove thigh cns driver
        for dr_dp in dr_dps:
            dr = rig.animation_data.drivers.find(dr_dp)
            if dr:
                rig.data.driver_remove(dr_dp, -1)
        
        # remove thigh cns
        cns = thigh_ik.constraints.get(cns_limit_name)
        if cns:
            thigh_ik.constraints.remove(cns)
        
        # remove leg cns
        cns = leg_ik.constraints.get(cns_limit_name)
        if cns:
            leg_ik.constraints.remove(cns)
            
        # untag pose bone as soft ik        
        if prop_name in c_foot_ik.keys():
            del c_foot_ik[prop_name]
            
    
    bpy.ops.object.mode_set(mode='EDIT')
    

def set_three_bones_leg(enabled):   
    # get the bone side
    side = ""
    if len(get_selected_edit_bones()) > 0:
        b_name = get_selected_edit_bones()[0].name
        side = get_bone_side(b_name)
    else:
        print("No bone selected")
        return

    thigh_b_ref_name = "thigh_b_ref" + side
    thigh_b = get_edit_bone("c_thigh_b" + side)
    thigh_ref = get_edit_bone("thigh_ref" + side)

    if enabled:
        # is the thigh_b_ref bone there?
        thigh_b_ref = get_edit_bone("thigh_b_ref" + side)
        if thigh_b_ref == None:
            thigh_b_ref = create_edit_bone(thigh_b_ref_name)
            thigh_ref = get_edit_bone("thigh_ref" + side)
            thigh_b_ref.head = thigh_ref.head - ((thigh_ref.tail - thigh_ref.head) * 0.5)
            thigh_b_ref.tail = thigh_ref.head.copy()
            thigh_b_ref.roll = thigh_ref.roll

            thigh_b_ref.use_deform = False

            set_bone_layer(thigh_b_ref, 17)

            # enable c_thigh_b deform
            thigh_b.use_deform = True

            # parent it
            thigh_b_ref.parent = thigh_ref.parent
            thigh_ref.parent = thigh_b_ref
            thigh_ref.use_connect = True
            
        # set duplicable
        thigh_b_ref['arp_duplicate'] = 1.0
        
    else:
        # disable it
        thigh_b_ref = get_edit_bone("thigh_b_ref" + side)
        if thigh_b_ref:
            # parent
            thigh_ref.use_connect = False
            thigh_ref.parent = thigh_b_ref.parent
            # delete bone
            delete_edit_bone(thigh_b_ref)

        # enable c_thigh_b deform
        thigh_b.use_deform = False


def set_leg_ik_offset(enabled):    
    rig = bpy.context.active_object
  
    # get the bone side
    side = ""
    if len(get_selected_edit_bones()) > 0:
        b_name = get_selected_edit_bones()[0].name
        side = get_bone_side(b_name)
    else:
        print("No bone selected")
    
    ik_offset_name = "c_foot_ik_offset" + side
    ch_list = ["c_foot_roll" + side, "c_foot_bank_01" + side, "foot_ik" + side]

    if enabled:
        # add the IK offset controllers
        ik_offset = get_edit_bone(ik_offset_name)
        created = False
        if ik_offset == None:
            # create it
            ik_offset = rig.data.edit_bones.new(ik_offset_name)
            created = True
            ik_offset.use_deform = False

        # set parents
        foot_ik = get_edit_bone("c_foot_ik" + side)
        ik_offset.parent = foot_ik
        toes_pivot = get_edit_bone("c_toes_pivot" + side)

        for c in ch_list:
            c_bone = get_edit_bone(c)
            if c_bone.parent != toes_pivot:
                c_bone.parent = ik_offset

        if toes_pivot:
            toes_pivot.parent = ik_offset

        # set coords
        heel_ref = get_edit_bone('foot_heel_ref' + side)
        toes_ref = get_edit_bone("toes_ref" + side)
        foot_ref = get_edit_bone("foot_ref" + side)
        ik_offset.head = foot_ref.head
        ik_offset.tail = foot_ref.head + (heel_ref.y_axis) * (heel_ref.head - toes_ref.tail).length / 2.5
        ik_offset.roll = heel_ref.roll

        # set layers
        set_bone_layer(ik_offset, 1)

        if created:
            # set shape
            bpy.ops.object.mode_set(mode='POSE')
            
            ik_offset_pbone = get_pose_bone(ik_offset_name)
            cs_name = "cs_c_foot_ik_offset"
            cs = bpy.data.objects.get(cs_name)
            if cs == None:
                append_from_arp(nodes=[cs_name], type="object")
                cs = bpy.data.objects.get(cs_name)

            ik_offset_pbone.custom_shape = cs

            # set rotation mode
            ik_offset_pbone.rotation_mode = 'XYZ'

            # set groups
            grp = rig.pose.bone_groups.get('body' + side[-2:])
            ik_offset_pbone.bone_group = grp

            # set driver
            c_foot_ik_name = ard.leg_bones_dict['foot']['control_ik']
            dr_foot_data_path = 'pose.bones["'+c_foot_ik_name + side + '"].'+get_custom_shape_scale_prop_name()
            dr_offset_data_path = dr_foot_data_path.replace(c_foot_ik_name + side, ik_offset_name)
            
            arr_ids = [-1]
            if bpy.app.version >= (3,0,0):
                arr_ids = [0, 1, 2]
            
            for idx in arr_ids:
                dr_foot = rig.animation_data.drivers.find(dr_foot_data_path, index=idx)
                dr_offset = rig.animation_data.drivers.find(dr_offset_data_path, index=idx)

                if dr_offset == None:
                    dr_offset = rig.animation_data.drivers.from_existing(src_driver=dr_foot)
                    dr_offset.data_path = dr_offset_data_path

            bpy.ops.object.mode_set(mode='EDIT')


    # disable
    else:
        # remove the IK offset controller
        ik_offset = get_edit_bone(ik_offset_name)
        toes_pivot = get_edit_bone("c_toes_pivot" + side)

        if ik_offset:
            # set parents
            foot_ik = get_edit_bone("c_foot_ik" + side)

            for c in ch_list:
                c_bone = get_edit_bone(c)
                if c_bone.parent != toes_pivot:
                    c_bone.parent = foot_ik

            if toes_pivot:
                toes_pivot.parent = foot_ik

            # delete it
            delete_edit_bone(ik_offset)

        remove_invalid_drivers()
    # end set_leg_ik_offset()
    
    
def set_arm_fk_lock(enabled):
    print("Setting Arm FK Lock...")
    side = ""
    if len(get_selected_edit_bones()):
        b_name = get_selected_edit_bones()[0].name
        side = get_bone_side(b_name)
    else:
        print("No bone selected, can't set arm_fk_lock")
        return

    arm_ref_name = (ard.arm_ref_dict['arm'])+side
    arm_ref = get_edit_bone(arm_ref_name)

    # store setting as custom prop
    arm_ref['arm_fk_lock'] = enabled

    rig = bpy.context.active_object
    constraint_name = 'ArmLock'
    prop_lock_name = 'arm_lock'

    bpy.ops.object.mode_set(mode='POSE')

    c_shoulder_name = (ard.arm_bones_dict['shoulder']['control'])+side
    c_shoulder = get_pose_bone(c_shoulder_name)
    c_arm_fk_name = (ard.arm_bones_dict['arm']['control_fk'])+side
    c_arm_fk = get_pose_bone(c_arm_fk_name)

    if enabled:
        print("  enabled")
        # create lock property
        if not prop_lock_name in c_shoulder.keys():
            create_custom_prop(node=c_shoulder, prop_name=prop_lock_name, prop_val=1, prop_min=0, prop_max=1, default=1, prop_description="Lock or free the arm FK rotation")

        # add armature constraint
        cns_arm = c_arm_fk.constraints.get(constraint_name)
        if cns_arm == None:
            # create the constraint
            cns_arm = c_arm_fk.constraints.new("ARMATURE")
            cns_arm.name = constraint_name

            # move up the constraint first in the stack
            move_constraint(c_arm_fk, cns_arm, 'UP', len(c_arm_fk.constraints))

        #   add targets
        print("  add targets")
        while len(cns_arm.targets) < 2:
            tar = cns_arm.targets.new()
            tar.target = rig
            # set subtarget when Match to Rig, depends on shoulder ref bone parent. Don't set them here

        #   add targets drivers
        print("  add drivers")
        for i, tar in enumerate(cns_arm.targets):
            dr_dp = 'pose.bones["' + c_arm_fk_name + '"].constraints["'+constraint_name+'"].targets['+str(i)+'].weight'
            tar_dp = 'pose.bones["' + c_shoulder_name + '"].["'+prop_lock_name+'"]'
            expr = 'var' if i == 0 else '1-var'
            add_driver_to_prop(rig, dr_dp, tar_dp, exp=expr)


    else:# arm lock disabled
        cns = c_arm_fk.constraints.get(constraint_name)
        if cns:
            # remove the constraint drivers
            for dr in rig.animation_data.drivers:
                cns_dr_dp = 'pose.bones["' + c_arm_fk_name + '"].constraints["'+constraint_name+'"].'
                if dr.data_path.startswith(cns_dr_dp):
                    rig.animation_data.drivers.remove(dr)

            # remove the constraint
            c_arm_fk.constraints.remove(cns)

        # remove the custom prop
        if prop_lock_name in c_shoulder.keys():
            del c_shoulder[prop_lock_name]


    bpy.ops.object.mode_set(mode='EDIT')
    print("Arm FK lock set.")
    

def set_arm_ik_offset(enabled): 
    # get the bone side
    side = ""
    if len(get_selected_edit_bones()) > 0:
        b_name = get_selected_edit_bones()[0].name
        side = get_bone_side(b_name)
    else:
        print("No bone selected")

    ik_offset_name = "c_hand_ik_offset" + side
    rig = bpy.context.active_object
    
    if enabled:
        # add the IK offset controllers
        ik_offset = get_edit_bone(ik_offset_name)
        created = False
        if ik_offset == None:
            # create it
            ik_offset = rig.data.edit_bones.new(ik_offset_name)
            created = True

        # set parents
        hand_ik = get_edit_bone("c_hand_ik" + side)
        ik_offset.parent = hand_ik

        # set coords
        ik_offset.head = hand_ik.head
        ik_offset.tail = hand_ik.tail
        ik_offset.roll = hand_ik.roll

        # set layers
        set_bone_layer(ik_offset, 1)

        if created:
            # set shape
            bpy.ops.object.mode_set(mode='POSE')
            ik_offset_pbone = get_pose_bone(ik_offset_name)
            cs_name = "cs_c_foot_ik_offset"
            cs = bpy.data.objects.get(cs_name)
            if cs == None:
                append_from_arp(nodes=[cs_name], type="object")
                cs = bpy.data.objects.get(cs_name)

            ik_offset_pbone.custom_shape = cs

            # set rotation mode
            ik_offset_pbone.rotation_mode = 'XYZ'

            # set groups
            grp = rig.pose.bone_groups.get('body' + side[-2:])
            ik_offset_pbone.bone_group = grp
            
            c_hand_ik_name = ard.arm_bones_dict['hand']['control_ik']
            
            # set driver
            dr_hand_data_path = 'pose.bones["'+c_hand_ik_name + side + '"].'+get_custom_shape_scale_prop_name()
            dr_offset_data_path = dr_hand_data_path.replace(c_hand_ik_name + side, ik_offset_name)
            
            arr_ids = [-1]
            if bpy.app.version >= (3,0,0):
                arr_ids = [0, 1, 2]
            
            for idx in arr_ids:            
                dr_hand = rig.animation_data.drivers.find(dr_hand_data_path, index=idx)
                dr_offset = rig.animation_data.drivers.find(dr_offset_data_path, index=idx)

                if dr_offset == None:
                    dr_offset = rig.animation_data.drivers.from_existing(src_driver=dr_hand)
                    dr_offset.data_path = dr_offset_data_path

            # set constraints
            forearm_ik_nostr = get_pose_bone("forearm_ik_nostr" + side)
            forearm_ik_nostr.constraints["IK"].subtarget = ik_offset_name
            forearm_ik = get_pose_bone("forearm_ik" + side)
            forearm_ik.constraints["IK"].subtarget = ik_offset_name
            hand = get_pose_bone("hand" + side)
            hand.constraints["rotIK"].subtarget = ik_offset_name
            hand.constraints["scaleIK"].subtarget = ik_offset_name

            bpy.ops.object.mode_set(mode='EDIT')

    # TODO disable
    # + align Match to Rig
    """
    else:
        # remove the IK offset controller
        ik_offset = get_edit_bone(ik_offset_name)
        toes_pivot = get_edit_bone("c_toes_pivot" + side)

        if ik_offset:
            # set parents
            foot_ik = get_edit_bone("c_foot_ik" + side)

            for c in ch_list:
                c_bone = get_edit_bone(c)
                if c_bone.parent != toes_pivot:
                    c_bone.parent = foot_ik

            if toes_pivot:
                toes_pivot.parent = foot_ik

            # delete it
            delete_edit_bone(ik_offset)

        remove_invalid_drivers()

    """
    #end set_arm_ik_offset()


def set_arm_ikfk_default(value):
    context = bpy.context
    active_bone_name = context.selected_editable_bones[0].name
    side = get_bone_side(active_bone_name)
    
    hand_ref_name = ard.arm_ref_dict['hand']
    hand_ref = get_edit_bone(hand_ref_name+side)
    
    if hand_ref == None:
        return
    
    hand_ref['ikfk_default'] = value
    
    
def set_leg_ikfk_default(value):
    
    context = bpy.context
    active_bone_name = context.selected_editable_bones[0].name
    side = get_bone_side(active_bone_name)
    
    foot_ref_name = ard.leg_ref_bones_dict['foot']
    foot_ref = get_edit_bone(foot_ref_name+side)
    
    if foot_ref == None:
        return
    
    foot_ref['ikfk_default'] = value
    
    
def set_toes_pivot(enabled):  
    context = bpy.context    
    active_bone = context.active_object.data.edit_bones.active.name
    rig_name = bpy.context.active_object.name
    rig = get_object(rig_name)

    # get the bone side
    side = ""
    if len(get_selected_edit_bones()) > 0:
        b_name = get_selected_edit_bones()[0].name
        side = get_bone_side(b_name)
    else:
        print("No bone selected")

    toes_pivot_name = "c_toes_pivot" + side
    toes_pivot_bone = get_edit_bone(toes_pivot_name)
    foot_ref = get_edit_bone("foot_ref" + side)
    foot_heel_ref = get_edit_bone("foot_heel_ref" + side)
    foot_bank = get_edit_bone("c_foot_bank_01" + side)
    c_foot_ik_name = ard.leg_bones_dict['foot']['control_ik']
    foot_ik = get_edit_bone(c_foot_ik_name + side)
    ik_offset_name = "c_foot_ik_offset" + side

    # create it
    if enabled:
        if foot_ref == None or foot_heel_ref == None or foot_ik == None:
            print("Missing feet bones, exit")
            return

        created = False
        if toes_pivot_bone == None:
            # create
            toes_pivot_bone = rig.data.edit_bones.new(toes_pivot_name)
            created = True
            # set transforms
            toes_pivot_bone.head = foot_ref.tail
            toes_pivot_bone.tail = foot_ref.tail - (foot_heel_ref.z_axis.normalized()) * (
                    foot_ref.head - foot_ref.tail).magnitude * 0.5
            toes_pivot_bone.use_deform = False

        # set parents
        ik_offset = get_edit_bone(ik_offset_name + side)
        if ik_offset == None:
            toes_pivot_bone.parent = foot_ik
        else:
            toes_pivot_bone.parent = ik_offset

        foot_bank.parent = toes_pivot_bone

        # set layers
        toes_pivot_bone.layers[1] = True
        for idx, lay in enumerate(toes_pivot_bone.layers):
            if idx != 1:
                toes_pivot_bone.layers[idx] = False

        if created:
            # set custom shape
            bpy.ops.object.mode_set(mode='POSE')
            toes_pivot_pbone = get_pose_bone(toes_pivot_name)
            toes_pivot_pbone.custom_shape = get_object("cs_torus_01")
            get_data_bone(toes_pivot_name).show_wire = True
            # set groups
            grp = rig.pose.bone_groups.get('body' + side[-2:])
            toes_pivot_pbone.bone_group = grp
            # rotation mode
            toes_pivot_pbone.rotation_mode = "XYZ"            
            
            
            # set driver
            dr_foot_data_path = 'pose.bones["' + c_foot_ik_name + side + '"].'+get_custom_shape_scale_prop_name()
            dr_pivot_data_path = dr_foot_data_path.replace(c_foot_ik_name + side, toes_pivot_name)

            arr_ids = [-1]
            if bpy.app.version >= (3,0,0):
                arr_ids = [0, 1, 2]
            
            for idx in arr_ids:            
                dr_foot = rig.animation_data.drivers.find(dr_foot_data_path, index=idx)
                dr_pivot = rig.animation_data.drivers.find(dr_pivot_data_path, index=idx)

                if dr_pivot == None:
                    dr_pivot = rig.animation_data.drivers.from_existing(src_driver=dr_foot)
                    dr_pivot.data_path = dr_pivot_data_path

            bpy.ops.object.mode_set(mode='EDIT')

    # delete it
    else:
        toes_pivot_bone = get_edit_bone("c_toes_pivot" + side)
        ik_offset = get_edit_bone(ik_offset_name + side)

        if toes_pivot_bone:
            # set parents
            if ik_offset == None:
                foot_bank.parent = foot_ik
            else:
                foot_bank.parent = ik_offset

            # delete
            delete_edit_bone(toes_pivot_bone)

        remove_invalid_drivers()
    # end set_toes_pivot()


def set_toes(thumb, index, middle, ring, pinky):
    context = bpy.context
    active_bone = context.active_object.data.edit_bones.active.name
    rig_name = bpy.context.active_object.name
    rig = get_object(rig_name)
    
    side = ""
    if len(get_selected_edit_bones()) > 0:
        b_name = get_selected_edit_bones()[0].name
        side = get_bone_side(b_name)
    else:
        print("No bone selected")

    foot_ref = get_edit_bone("foot_ref" + side)

    if not foot_ref:
        print("foot_ref" + side, "does not exist, cannot create toes")
        return

    foot_ref_dir = (foot_ref.tail - foot_ref.head)

    # disable X Mirror
    xmirror_state = bpy.context.object.data.use_mirror_x
    bpy.context.object.data.use_mirror_x = False

    # disable the proxy picker to avoid bugs
    proxy_picker_state = disable_proxy_picker()

    # Active all layers
    layers_select = enable_all_armature_layers()

    def create_toe(toe_type=""):
        if toe_type == "thumb":
            toe_ref_list = ard.toes_thumb_ref_list
            toe_control_list = ard.toes_thumb_control_list

        if toe_type == "index":
            toe_ref_list = ard.toes_index_ref_list
            toe_control_list = ard.toes_index_control_list

        if toe_type == "middle":
            toe_ref_list = ard.toes_middle_ref_list
            toe_control_list = ard.toes_middle_control_list

        if toe_type == "ring":
            toe_ref_list = ard.toes_ring_ref_list
            toe_control_list = ard.toes_ring_control_list

        if toe_type == "pinky":
            toe_ref_list = ard.toes_pinky_ref_list
            toe_control_list = ard.toes_pinky_control_list

        exist_already = False
        if get_edit_bone(toe_ref_list[0] + side):
            exist_already = True

        if not exist_already:
            #print("toe", toe_type, "does not exist, create bones")
            type = toe_type + side[-2:]
            addon_directory = os.path.dirname(os.path.abspath(__file__))
            filepath = addon_directory + "/armature_presets/modules.blend"

            # make a list of current custom shapes objects in the scene for removal later
            cs_objects = [obj.name for obj in bpy.data.objects if obj.name.startswith('cs_')]

            # load the objects in the blend file datas
            with bpy.data.libraries.load(filepath, link=False) as (data_from, data_to):
                # only import the necessary armature
                data_to.objects = [i for i in data_from.objects if i == "rig_toes_" + type]

            # link in scene
            for obj in data_to.objects:
                context.scene.collection.objects.link(obj)
                #print("Linked armature:", obj.name)
            bpy.ops.object.mode_set(mode='OBJECT')

            # replace custom shapes by custom shapes already existing in the scene
            set_active_object('rig_toes_' + type)
            
            rig_toe = get_object('rig_toes_' + type)
            
            bpy.ops.object.mode_set(mode='POSE')
            
            for b in bpy.context.active_object.pose.bones:
                #print("Checking bone", b.name)
                
                if b.custom_shape:
                    #print("Custom shape:", b.name, b.custom_shape.name)
                    if b.custom_shape.name not in cs_objects:
                        if b.custom_shape.name.replace('.001', '') in cs_objects:
                            #print("replaced shape", b.name, b.custom_shape.name)
                            b.custom_shape = get_object(b.custom_shape.name.replace('.001', ''))
                
                

                # naming
                if "_dupli_" in side:
                    b.name = b.name.split('.')[0] + side

            # find added/useless custom shapes and delete them
            """
            for obj in bpy.data.objects:
                if obj.name.startswith('cs_'):
                    if not obj.name in cs_objects:
                        #print("delete custom shape", obj.name)
                        bpy.data.objects.remove(obj, do_unlink=True)
            """

            bpy.ops.object.mode_set(mode='OBJECT')

            rig_toe.matrix_world = rig.matrix_world.copy()
            
            # Merge to the main armature
            bpy.ops.object.select_all(action='DESELECT')
            set_active_object('rig_toes_' + type)
            set_active_object(rig_name)
            bpy.ops.object.mode_set(mode='OBJECT')
            bpy.ops.object.join()

            # Parent lost bones
            bpy.ops.object.mode_set(mode='EDIT')
            for bn in bpy.context.active_object.data.edit_bones:
                if len(bn.keys()) > 0:
                    if "arp_parent" in bn.keys():
                        parent_prop = get_edit_bone(bn["arp_parent"].split(".")[0] + side)
                        if bn.parent == None and parent_prop:
                            bn.parent = parent_prop

            skeleton = bpy.context.active_object
            
            # Move all new toe bones near the foot
            b1 = get_edit_bone(toe_ref_list[0] + side)
            
            if len(b1.keys()) > 0:
                if "arp_offset_matrix" in b1.keys():                    
                    foot_ref = get_edit_bone("toes_ref" + side)
                    b1_local = Matrix(b1["arp_offset_matrix"]) @ b1.matrix

                    # store children bones matrix
                    children_bones = toe_ref_list + toe_control_list
                    children_bones.remove(toe_ref_list[0])
                    children_mat_dict = {}
                    for child_name in children_bones:
                        b2 = get_edit_bone(child_name + side)
                        children_mat_dict[b2] = b1.matrix.inverted() @ b2.matrix

                    # move b1
                    b1.matrix = foot_ref.matrix @ b1_local
                    # move other bones
                    for child_ in children_mat_dict:
                        child_.matrix = b1.matrix @ children_mat_dict[child_]

                    # store current bones coords copy in a new dict to avoid the multiple transform issue when bones have connected parent
                    bones_coords = {}
                    for b in children_mat_dict:
                        bones_coords[b] = b.head.copy(), b.tail.copy()

                    # scale proportionally to the head bone
                    scale_from_origin(ed_bone=b1, center=foot_ref.head, factor=(foot_ref.tail - foot_ref.head).magnitude * 19)

                    for eb in bones_coords:
                        scale_from_origin(ed_bone=eb, center=foot_ref.head, head_coords=bones_coords[eb][0],
                                          tail_coords=bones_coords[eb][1],
                                          factor=(foot_ref.tail - foot_ref.head).magnitude * 19)

        #else:
        #    print(toe_type, "already created")


        # -- End function create_toe()

    def disable_toe(toe_type=""):
        if toe_type == "thumb":
            toe_ref_list = ard.toes_thumb_ref_list
            toe_control_list = ard.toes_thumb_control_list

        if toe_type == "index":
            toe_ref_list = ard.toes_index_ref_list
            toe_control_list = ard.toes_index_control_list

        if toe_type == "middle":
            toe_ref_list = ard.toes_middle_ref_list
            toe_control_list = ard.toes_middle_control_list

        if toe_type == "ring":
            toe_ref_list = ard.toes_ring_ref_list
            toe_control_list = ard.toes_ring_control_list

        if toe_type == "pinky":
            toe_ref_list = ard.toes_pinky_ref_list
            toe_control_list = ard.toes_pinky_control_list

        # delete bones
        for bname in toe_ref_list + toe_control_list:
            toe_bone = get_edit_bone(bname + side)
            if toe_bone:
                delete_edit_bone(toe_bone)

        if 'b_name' in locals():
            del b_name

        # proxy picker bones
        for bname in toe_control_list:
            toe_picker = get_edit_bone(bname + "_proxy" + side)
            if toe_picker:
                switch_bone_layer(toe_picker.name, 0, 22, False)
                
        # make sure to select at least one bone if the current one has been deleted                
        bpy.context.active_object.data.edit_bones.active = get_edit_bone("foot_ref"+side)

    # Set toes
    if not thumb:
        disable_toe(toe_type="thumb")
    else:
        create_toe(toe_type="thumb")

    if not index:
        disable_toe(toe_type="index")
    else:
        create_toe(toe_type="index")

    if not middle:
        disable_toe(toe_type="middle")
    else:
        create_toe(toe_type="middle")

    if not ring:
        disable_toe(toe_type="ring")
    else:
        create_toe(toe_type="ring")

    if not pinky:
        disable_toe(toe_type="pinky")
    else:
        create_toe(toe_type="pinky")

    # Restore layers
    restore_armature_layers(layers_select)

    # restore X Mirror state
    bpy.context.object.data.use_mirror_x = xmirror_state

    # restore picker
    restore_proxy_picker(proxy_picker_state)

    # end set_toes()


def set_fingers(thumb, index, middle, ring, pinky, independent_pinky=False, fingers_ik=False, fingers_ik_shape="cs_sphere", fingers_ik_color=(0.8, 0.432, 0.0), fingers_ik2_shape="cs_sphere", fingers_ik2_color=(0.8, 0.432, 0.0), fingers_ik_parent="hand", fingers_ik_pole_parent="hand", fingers_ik_pole_shape="cs_sphere", fingers_ik_pole_color=(1.0, 0.9, 0.9), fingers_ik_pole_distance=1.0):
   
    arr_ids = [-1]
    if bpy.app.version >= (3,0,0):
        arr_ids = [0,1,2]
                    
    context = bpy.context
    active_bone = context.active_object.data.edit_bones.active.name
    rig_name = bpy.context.active_object.name
    rig = get_object(context.active_object.name)

    # get the bone side
    side = ""
    if len(get_selected_edit_bones()) > 0:
        b_name = get_selected_edit_bones()[0].name
        side = get_bone_side(b_name)
    else:
        print("No bone selected")

    hand_ref = get_edit_bone("hand_ref" + side)

    if not hand_ref:
        print("hand_ref" + side, "does not exist, cannot create fingers")
        return

    # disable the proxy picker to avoid bugs
    proxy_picker_state = disable_proxy_picker()

    # Active all layers
    layers_select = enable_all_armature_layers()

    # save settings in props
    hand_ref["fingers_ik"] = fingers_ik
    hand_ref["fingers_ik_shape"] = fingers_ik_shape
    hand_ref["fingers_ik2_shape"] = fingers_ik2_shape
    hand_ref["fingers_ik_parent"] = fingers_ik_parent
    hand_ref["fingers_ik_pole_parent"] = fingers_ik_pole_parent
    hand_ref["fingers_ik_pole_shape"] = fingers_ik_pole_shape
    hand_ref["fingers_ik_color"] = fingers_ik_color
    hand_ref["fingers_ik2_color"] = fingers_ik2_color
    hand_ref["fingers_ik_pole_color"] = fingers_ik_pole_color
    hand_ref["fingers_ik_pole_distance"] = fingers_ik_pole_distance
    #get_data_bone("hand_ref"+side)["fingers_ik_pole_distance"] = fingers_ik_pole_distance# also store on data bone for access on pose mode

    def create_finger(finger_type=""):
        if finger_type == "thumb":
            finger_ref_list = ard.thumb_ref_list
            finger_control_list = ard.thumb_control_list
            finger_intern_list = ard.thumb_intern_list

        if finger_type == "index":
            finger_ref_list = ard.index_ref_list
            finger_control_list = ard.index_control_list
            finger_intern_list = ard.index_intern_list

        if finger_type == "middle":
            finger_ref_list = ard.middle_ref_list
            finger_control_list = ard.middle_control_list
            finger_intern_list = ard.middle_intern_list

        if finger_type == "ring":
            finger_ref_list = ard.ring_ref_list
            finger_control_list = ard.ring_control_list
            finger_intern_list = ard.ring_intern_list

        if finger_type == "pinky":
            finger_ref_list = ard.pinky_ref_list
            finger_control_list = ard.pinky_control_list
            finger_intern_list = ard.pinky_intern_list

        exist_already = False
        if get_edit_bone(finger_ref_list[0] + side):
            exist_already = True

        if not exist_already:
            print(finger_type, "does not exist, create bones")
            type = finger_type + side[-2:]
            addon_directory = os.path.dirname(os.path.abspath(__file__))
            filepath = addon_directory + "/armature_presets/modules.blend"

            # make a list of current custom shapes objects in the scene for removal later
            cs_objects = [obj.name for obj in bpy.data.objects if obj.name[:3] == "cs_"]

            # load the objects in the blend file datas
            with bpy.data.libraries.load(filepath, link=False) as (data_from, data_to):
                # only import the necessary armature
                data_to.objects = [i for i in data_from.objects if i == "rig_" + type]

            # link in scene
            for obj in data_to.objects:
                context.scene.collection.objects.link(obj)
                print("Linked armature:", obj.name)
                
            bpy.ops.object.mode_set(mode='OBJECT')

            # replace custom shapes by custom shapes already existing in the scene
            set_active_object('rig_' + type)
            rig_finger = get_object('rig_' + type)
            
            bpy.ops.object.mode_set(mode='POSE')
            
            for b in bpy.context.active_object.pose.bones:
                if b.custom_shape:
                    if b.custom_shape.name not in cs_objects:
                        if b.custom_shape.name.replace('.001', '') in cs_objects:
                            b.custom_shape = bpy.data.objects[b.custom_shape.name.replace('.001', '')]

                # naming
                if "_dupli_" in side:
                    b.name = b.name.split('.')[0] + side

                # set constraints
                if len(b.constraints) > 0:
                    for cns in b.constraints:
                        try:
                            if cns.target == None:
                                cns.target = bpy.data.objects[rig_name]
                            if "_dupli_" in side:
                                if cns.subtarget == "hand" + side[-2:]:
                                    cns.subtarget = "hand" + side
                        except:
                            pass

            # replace drivers variables
            for dr in bpy.context.active_object.animation_data.drivers:
                if 'pose.bones' in dr.data_path:
                    b = dr.data_path.split('"')[1]
                    if side in b and b.replace(side, "") in finger_intern_list:

                        for var in dr.driver.variables:
                            for tar in var.targets:
                                if not side in tar.data_path:
                                    #print("replaced driver var data path")
                                    #print(tar.data_path)
                                    tar.data_path = tar.data_path.replace(side[-2:], side)
                                    #print("=>", tar.data_path)

            # find added/useless custom shapes and delete them
            """
            for obj in bpy.data.objects:
                if obj.name.startswith("cs_"):
                    if not obj.name in cs_objects:
                        bpy.data.objects.remove(obj, do_unlink=True)
            """

            bpy.ops.object.mode_set(mode='OBJECT')
            
            rig_finger.matrix_world = rig.matrix_world.copy()

            # Merge to the main armature
            bpy.ops.object.select_all(action='DESELECT')
            set_active_object('rig_' + type)
            set_active_object(rig_name)
            bpy.ops.object.mode_set(mode='OBJECT')
            bpy.ops.object.join()

            # Parent lost bones
            bpy.ops.object.mode_set(mode='EDIT')
            for bn in bpy.context.active_object.data.edit_bones:
                if len(bn.keys()) > 0:
                    if "arp_parent" in bn.keys():
                        parent_prop = get_edit_bone(bn["arp_parent"].split(".")[0] + side)
                        if bn.parent == None and parent_prop:
                            bn.parent = parent_prop                            
            
            
            # Move all new finger bones near the hand
            b1 = get_edit_bone(finger_ref_list[0] + side)
            
            if len(b1.keys()) > 0:
                if "arp_offset_matrix" in b1.keys():                    
                    hand_ref = get_edit_bone("hand_ref" + side)
                    b1_local = Matrix(b1["arp_offset_matrix"]) @ b1.matrix

                    # store children bones matrix
                    children_bones = finger_ref_list + finger_control_list + finger_intern_list
                    children_bones.remove(finger_ref_list[0])
                    children_mat_dict = {}
                    for child_name in children_bones:
                        b2 = get_edit_bone(child_name+side)
                        if b2 == None:# optional bones, ik fingers...
                            continue
                        children_mat_dict[b2] = b1.matrix.inverted() @ b2.matrix

                    # move b1
                    b1.matrix = hand_ref.matrix @ b1_local
                    # move other bones
                    for child_ in children_mat_dict:
                        child_.matrix = b1.matrix @ children_mat_dict[child_]

                    # store current bones coords copy in a new dict to avoid the multiple transform issue when bones have connected parent
                    bones_coords = {}
                    for b in children_mat_dict:
                        bones_coords[b] = b.head.copy(), b.tail.copy()

                    # scale proportionally to the head bone
                    scale_from_origin(ed_bone=b1, center=hand_ref.head, factor=(hand_ref.tail - hand_ref.head).magnitude * 19)

                    for eb in bones_coords:
                        scale_from_origin(ed_bone=eb, center=hand_ref.head, head_coords=bones_coords[eb][0],
                                          tail_coords=bones_coords[eb][1],
                                          factor=(hand_ref.tail - hand_ref.head).magnitude * 19)

        #else:
        #    print(finger_type, "already created")
        # -- end create_finger()

    def disable_finger(finger_type=""):
        if finger_type == "thumb":
            finger_ref_list = ard.thumb_ref_list
            finger_control_list = ard.thumb_control_list
            finger_intern_list = ard.thumb_intern_list

        if finger_type == "index":
            finger_ref_list = ard.index_ref_list
            finger_control_list = ard.index_control_list
            finger_intern_list = ard.index_intern_list

        if finger_type == "middle":
            finger_ref_list = ard.middle_ref_list
            finger_control_list = ard.middle_control_list
            finger_intern_list = ard.middle_intern_list

        if finger_type == "ring":
            finger_ref_list = ard.ring_ref_list
            finger_control_list = ard.ring_control_list
            finger_intern_list = ard.ring_intern_list

        if finger_type == "pinky":
            finger_ref_list = ard.pinky_ref_list
            finger_control_list = ard.pinky_control_list
            finger_intern_list = ard.pinky_intern_list


        # IK bones
            # chain
        for fi in range(1, 4):
            finger_control_list.append("c_"+finger_type+str(fi)+"_ik")

            # target
        finger_control_list.append("c_"+finger_type+"_ik")
        finger_control_list.append("c_"+finger_type+"_ik2")

            # pole
        finger_control_list.append("c_"+finger_type+"_pole")


        # Delete bones
        for bname in finger_ref_list + finger_control_list + finger_intern_list:
            finger_bone = get_edit_bone(bname + side)
            if finger_bone:
                delete_edit_bone(finger_bone)

        if 'b_name' in locals():
            del b_name

        # proxy picker bones
        for bname in finger_control_list:
            finger_picker = get_edit_bone(bname + "_proxy" + side)
            if finger_picker:
                switch_bone_layer(finger_picker.name, 0, 22, False)

        # make sure to select at least one bone if the current one has been deleted        
        #if context.active_object.data.edit_bones.active == None or len(get_selected_edit_bones()) == 0:         
        bpy.context.active_object.data.edit_bones.active = get_edit_bone("hand_ref"+side)

        bpy.ops.object.mode_set(mode='OBJECT')
        remove_invalid_drivers()
        bpy.ops.object.mode_set(mode='EDIT')


    # Set fingers

    if not thumb:
        disable_finger(finger_type="thumb")
    else:
        # create bones
        create_finger(finger_type="thumb")

    if not index:
        disable_finger(finger_type="index")
    else:
        # create bones
        create_finger(finger_type="index")

    if not middle:
        disable_finger(finger_type="middle")
    else:
        # create bones
        create_finger(finger_type="middle")

    if not ring:
        disable_finger(finger_type="ring")
    else:
        # create bones
        create_finger(finger_type="ring")

    if not pinky:
        disable_finger(finger_type="pinky")
    else:
        # create bones
        create_finger(finger_type="pinky")

        # independent pinky
        if independent_pinky:
            # create the c_pinky1_auto bone
            pinky_auto_name = "c_pinky1_auto" + side
            pinky_auto = get_edit_bone(pinky_auto_name)
            if pinky_auto == None:
                pinky_auto = bpy.context.active_object.data.edit_bones.new(pinky_auto_name)
                # coords
                pinky_auto.head, pinky_auto.tail = [0, 0, 0], [0, 0, 1]

                # parent
                pinky_auto.parent = get_edit_bone("hand" + side)

                # layer
                set_bone_layer(pinky_auto, 0)

            bpy.ops.object.mode_set(mode='POSE')

            c_pinky1_auto = get_pose_bone(pinky_auto_name)
            c_pinky1_base = get_pose_bone("c_pinky1_base" + side)

            # rotation mode
            c_pinky1_auto.rotation_mode = "XYZ"

            # group color
            c_pinky1_auto.bone_group = bpy.context.active_object.pose.bone_groups.get("body"+side[-2:])

            # set the c_pinky1_auto shape
            c_pinky1_auto.custom_shape = get_object("cs_base_finger_end")
            set_custom_shape_scale(c_pinky1_auto, 0.4)
            # set the c_pinky1_base shape
            c_pinky1_base.custom_shape = get_object("cs_base_finger")

            # set constraints
            # pinky1_base
            cns = c_pinky1_base.constraints.get("Copy Rotation")
            if cns == None:
                cns = c_pinky1_base.constraints.new("COPY_ROTATION")
                cns.target = bpy.context.active_object
                cns.subtarget = pinky_auto_name
                cns.mix_mode = "OFFSET"
                cns.owner_space = cns.target_space = "LOCAL"

                # ring1_base
            c_ring1_base = get_pose_bone("c_ring1_base" + side)
            cns = c_ring1_base.constraints.get("Copy Rotation")
            cns.subtarget = pinky_auto_name

            # middle1_base
            c_middle1_base = get_pose_bone("c_middle1_base" + side)
            cns = c_middle1_base.constraints.get("Copy Rotation")
            cns.subtarget = pinky_auto_name

            bpy.ops.object.mode_set(mode='EDIT')


    # Fingers IK
    if fingers_ik:
        print("Set Fingers IK...")

        created_ik_bones = {} #[bone_name]:[bone type, ik_target_name, finger_type]

        # Create bones
        for fing_type in ["thumb", "index", "middle", "ring", "pinky"]:

            # skip if finger is not enabled
            phal1_ref = get_edit_bone(fing_type+"1_ref"+side)
            if phal1_ref == None:
                continue

            ik_target_name = "c_"+fing_type+"_ik"+side
            ik_target2_name = "c_"+fing_type+"_ik2"+side

            # IK chain
            for fi in range(1, 4):
                f_idx = str(fi)
                ref_name = fing_type+f_idx+"_ref"+side
                ref_bone = get_edit_bone(ref_name)
                c_ik_name = "c_"+fing_type+f_idx+"_ik"+side
                c_ik = get_edit_bone(c_ik_name)
                # create bone
                if c_ik == None:
                    c_ik = rig.data.edit_bones.new(c_ik_name)

                # store for convenience
                created_ik_bones[c_ik_name] = ["ik_chain", ik_target_name, fing_type]

                c_ik.head, c_ik.tail = [0,0,0], [0,0,1]

                # parent
                if fi == 1:
                    c_ik.parent = get_edit_bone("c_"+fing_type+f_idx+"_base"+side)
                else:
                    c_ik.parent = get_edit_bone("c_"+fing_type+str(fi-1)+"_ik"+side)
                # deform
                c_ik.use_deform = False
                # layer
                set_bone_layer(c_ik, 0)


            # IK target 1 (tip)
            c_ik_target = get_edit_bone(ik_target_name)
                # create bone
            if c_ik_target == None:
                c_ik_target = rig.data.edit_bones.new(ik_target_name)
                c_ik_target.head, c_ik_target.tail = [0,0,0], [0,0,1]

                # store for convenience
            created_ik_bones[ik_target_name] = ["ik_target", ik_target_name, fing_type]

                # deform
            c_ik_target.use_deform = False
                # layer
            set_bone_layer(c_ik_target, 0)


            # IK target 2 (root)
            c_ik_target2 = get_edit_bone(ik_target2_name)
                # create bone
            if c_ik_target2 == None:
                c_ik_target2 = rig.data.edit_bones.new(ik_target2_name)
                c_ik_target2.head, c_ik_target2.tail = [0,0,0], [0,0,1]

                # store for convenience
            created_ik_bones[ik_target2_name] = ["ik_target2", ik_target_name, fing_type]

                # deform
            c_ik_target2.use_deform = False
                # layer
            set_bone_layer(c_ik_target2, 0)


             # IK pole
            ik_pole_name = "c_"+fing_type+"_pole"+side
            c_ik_pole = get_edit_bone(ik_pole_name)
                # create bone
            if c_ik_pole == None:
                c_ik_pole = rig.data.edit_bones.new(ik_pole_name)
                c_ik_pole.head, c_ik_pole.tail = [0,0,0], [0,0,1]
            c_ik_pole.parent = None           

                # store for convenience
            created_ik_bones[ik_pole_name] = ["ik_pole", ik_target_name, fing_type]
                # deform
            c_ik_pole.use_deform = False
                # layer
            set_bone_layer(c_ik_pole, 0)


        # Align
        pole_angles_dict = {}

        align_fingers_ik(side, pole_angles_dict)

        # Pose Mode
        bpy.ops.object.mode_set(mode='POSE')

        drivers_list = rig.animation_data.drivers

        # set IK lock axes
        for ik_bone_name in created_ik_bones:
            type = created_ik_bones[ik_bone_name][0]
            fing_type = created_ik_bones[ik_bone_name][2]
            pb = get_pose_bone(ik_bone_name)
            if type == "ik_chain":
                if not "1_ik" in ik_bone_name:
                    pb.lock_ik_z = True

            if type == "ik_pole" or type == "ik_target":
                pb.lock_rotation = [True, True, True]
                pb.lock_scale = [True, True, True]

            if type == "ik_target2":
                pb.lock_scale = [True, True, True]

            # set properties
            root_finger_name = "c_"+fing_type+"1_base"+side
            root_finger = get_pose_bone(root_finger_name)

                # ik_fk_switch
            if not "ik_fk_switch" in root_finger.keys():
                create_custom_prop(node=root_finger, prop_name="ik_fk_switch", prop_val=0.0, prop_min=0.0, prop_max=1.0, soft_min=0.0, soft_max=1.0, prop_description="Finger IK-FK switch value")

                # ik_tip
            if not "ik_tip" in root_finger.keys():
                create_custom_prop(node=root_finger, prop_name="ik_tip", prop_val=0, prop_min=0, prop_max=1, soft_min=0, soft_max=1, prop_description="Use IK target at the tip of the finger if enabled, or at the root of the phalange if disabled")


        # set constraints
        for ik_bone_name in created_ik_bones:
            type = created_ik_bones[ik_bone_name][0]
            fing_type = created_ik_bones[ik_bone_name][2]
            root_finger_name = "c_"+fing_type+"1_base"+side
            pb = get_pose_bone(ik_bone_name)

            # Child Of constraints
            if type == "ik_target" or type == "ik_target2":
                cns1 = pb.constraints.get("Child Of_hand")
                if cns1 == None:
                    cns1 = pb.constraints.new("CHILD_OF")
                    cns1.name = "Child Of_hand"
                cns1.target = rig
                target_bname = ""
                if fingers_ik_parent == "hand":
                    target_bname = "hand"+side
                elif fingers_ik_parent == "metacarp":
                    target_bname = root_finger_name
                cns1.subtarget = target_bname

            elif type == "ik_pole":
                parent_bname = ""
                if fingers_ik_pole_parent == "hand":
                    parent_bname = "hand"+side
                elif fingers_ik_pole_parent == "metacarp":
                    parent_bname = "c_"+fing_type+"1_base"+side

                cns = pb.constraints.get("Child Of_pole")
                if cns == None:
                    cns = pb.constraints.new("CHILD_OF")
                    cns.name = "Child Of_pole"
                cns.target = rig
                cns.subtarget = parent_bname

            # IK constraint
            elif type == "ik_chain":

                if "3_ik" in ik_bone_name:
                    # IK 1 (tip)
                    cns_ik = pb.constraints.get("IK")
                    if cns_ik == None:
                        cns_ik = pb.constraints.new("IK")
                        cns_ik.name = "IK"
                        cns_ik.target = rig
                        ik_target_name = created_ik_bones[ik_bone_name][1]
                        cns_ik.subtarget = ik_target_name
                        cns_ik.chain_count = 3
                        cns_ik.use_tail = True
                        cns_ik.pole_target = rig
                        cns_ik.pole_subtarget = "c_"+fing_type+"_pole"+side

                    # drive influence
                    dr_dp = 'pose.bones["'+ik_bone_name+'"].constraints["IK"].influence'
                    tar_dp = 'pose.bones["' + root_finger_name + '"].["ik_tip"]'
                    add_driver_to_prop(rig, dr_dp, tar_dp, exp="var")

                    # drive mute (could fix some occasional, weird cyclic dependencies)
                    dr_dp = 'pose.bones["'+ik_bone_name+'"].constraints["IK"].enabled'
                    _exp = "var"
                    if bpy.app.version < (3,0,0):
                        dr_dp = 'pose.bones["'+ik_bone_name+'"].constraints["IK"].mute' 
                        _exp = "1-var"
                    tar_dp = 'pose.bones["' + root_finger_name + '"].["ik_tip"]'
                    add_driver_to_prop(rig, dr_dp, tar_dp, exp=_exp)

                    # drive chain length to get rid of cyclic dependency, as a workaround to disable the constraint
                    dr_dp = ''
                    dr_exp = ''
                    if bpy.app.version < (3,2,0):
                        dr_dp = 'pose.bones["'+ik_bone_name+'"].constraints["IK"].chain_count'
                        dr_exp = "3 if var == 1 else 1"
                    else:
                        dr_dp = 'pose.bones["'+ik_bone_name+'"].constraints["IK"].use_tail'# chain length is no more animatable in 3.2 and post, use the tail setting instead
                        dr_exp = "var"
                    tar_dp = 'pose.bones["' + root_finger_name + '"].["ik_tip"]'
                    add_driver_to_prop(rig, dr_dp, tar_dp, exp=dr_exp)
                    
                    cns_ik.use_stretch = False# update hack
                    cns_ik.use_stretch = True
                    
                    
                    # Copy Rotation for IK 2
                    cns_rot = pb.constraints.get("Copy Rotation")
                    if cns_rot == None:
                        cns_rot = pb.constraints.new("COPY_ROTATION")
                        cns_rot.name = "Copy Rotation"
                        cns_rot.target = rig
                        cns_rot.subtarget = "c_"+fing_type+"_ik2"+side

                        # drive influence
                    dr_dp = 'pose.bones["'+ik_bone_name+'"].constraints["Copy Rotation"].influence'
                    tar_dp = 'pose.bones["' + root_finger_name + '"].["ik_tip"]'
                    add_driver_to_prop(rig, dr_dp, tar_dp, exp="1-var")


                # IK 2 (root)
                if "2_ik" in ik_bone_name:
                    cns_ik2 = pb.constraints.get("IK")
                    if cns_ik2 == None:
                        cns_ik2 = pb.constraints.new("IK")
                        cns_ik2.name = "IK"
                        cns_ik2.target = rig
                        ik_target_name = created_ik_bones[ik_bone_name][1].replace("_ik", "_ik2")
                        cns_ik2.subtarget = ik_target_name
                        cns_ik2.chain_count = 2
                        cns_ik2.use_tail = True
                        cns_ik2.pole_target = rig
                        cns_ik2.pole_subtarget = "c_"+fing_type+"_pole"+side

                    # drive influence
                    dr_dp = 'pose.bones["'+ik_bone_name+'"].constraints["IK"].influence'
                    tar_dp = 'pose.bones["' + root_finger_name + '"].["ik_tip"]'
                    add_driver_to_prop(rig, dr_dp, tar_dp, exp="1-var")

                    # drive mute (could help to resolve cyclic dependencies)
                    dr_dp = 'pose.bones["'+ik_bone_name+'"].constraints["IK"].enabled'
                    _exp = "1-var"
                    if bpy.app.version < (3,0,0):
                        dr_dp = 'pose.bones["'+ik_bone_name+'"].constraints["IK"].mute'
                        _exp = "var"
                    tar_dp = 'pose.bones["' + root_finger_name + '"].["ik_tip"]'
                    add_driver_to_prop(rig, dr_dp, tar_dp, exp=_exp)

                    # drive chain length to get rid of cyclic dependency, as a workaround to disable the constraint
                    dr_dp = ''
                    dr_exp = ''                   
                    if bpy.app.version < (3,2,0):
                        dr_dp = 'pose.bones["'+ik_bone_name+'"].constraints["IK"].chain_count'
                        dr_exp = "1 if var == 1 else 2"
                    else:
                        dr_dp = 'pose.bones["'+ik_bone_name+'"].constraints["IK"].use_tail'# chain length is no more animatable in 3.2 and post, use the tail setting instead
                        dr_exp = "1-var"
                              
                    tar_dp = 'pose.bones["' + root_finger_name + '"].["ik_tip"]'
                    add_driver_to_prop(rig, dr_dp, tar_dp, exp=dr_exp)
                    
                    cns_ik2.use_stretch = False# update hack
                    cns_ik2.use_stretch = True
                   

                # FK Copy Rotation constraint
                fk_name = ik_bone_name.replace("_ik", "")
                b_fk = get_pose_bone(fk_name)
                copy_cns = b_fk.constraints.get("Rot_IK")
                if copy_cns == None:
                    copy_cns = b_fk.constraints.new("COPY_ROTATION")
                    copy_cns.name = "Rot_IK"
                    copy_cns.target = rig
                    copy_cns.subtarget = ik_bone_name

                #   drive influence
                dr_dp = 'pose.bones["'+fk_name+'"].constraints["Rot_IK"].influence'
                tar_dp = 'pose.bones["' + root_finger_name + '"].["ik_fk_switch"]'
                add_driver_to_prop(rig, dr_dp, tar_dp, exp="1-var")

                # FK Copy Scale constraint
                cns_scale = b_fk.constraints.get("Scale_IK")
                if cns_scale == None:
                    cns_scale = b_fk.constraints.new("COPY_SCALE")
                    cns_scale.name = "Scale_IK"
                    cns_scale.target = rig
                    cns_scale.subtarget = ik_bone_name
                    cns_scale.owner_space = cns_scale.target_space = "LOCAL"

                #   drive influence
                dr_dp = 'pose.bones["'+fk_name+'"].constraints["Scale_IK"].influence'
                tar_dp = 'pose.bones["' + root_finger_name + '"].["ik_fk_switch"]'
                add_driver_to_prop(rig, dr_dp, tar_dp, exp="1-var")


        normalized_custom_shapes = []# list normalized shapes to operate only once on them

        for ik_bone_name in created_ik_bones:
            type = created_ik_bones[ik_bone_name][0]
            pb_ik = get_pose_bone(ik_bone_name)
            fing_type = created_ik_bones[ik_bone_name][2]

            # set Euler rotation
            pb_ik.rotation_mode = "XYZ"

            # set custom shapes
            if type == "ik_chain":
                pb_fk = get_pose_bone(ik_bone_name.replace("_ik", ""))

                # only set shape if it hasn't been already edited by user
                set_shape = False
                if pb_ik.custom_shape == None:
                    set_shape = True
                elif not pb_ik.custom_shape.name.startswith("cs_user_"):
                    set_shape = True

                if set_shape:
                    pb_ik.custom_shape = pb_fk.custom_shape

                root_finger_name = "c_"+fing_type+"1_base"+side

                # IK shape scale driver
                dr_dp = 'pose.bones["'+ik_bone_name+'"].'+get_custom_shape_scale_prop_name()   
                tar_dp = 'pose.bones["' + root_finger_name + '"].["ik_fk_switch"]'
                
                for idx in arr_ids:
                    add_driver_to_prop(rig, dr_dp, tar_dp, exp="1-var", array_idx=idx)
                  

                # FK shape scale driver
                fk_bone_name = ik_bone_name.replace("_ik", "")

                dr_dp_fk = 'pose.bones["'+fk_bone_name+'"].'+get_custom_shape_scale_prop_name()
                
                for idx in arr_ids:
                    dr_fk = drivers_list.find(dr_dp_fk, index=idx)

                    if dr_fk == None:

                        # custom shapes scale must be normalized since the scale input will be driven in the [0,1] range
                        pb_fk_cs = pb_fk.custom_shape
                        if pb_fk_cs:
                            if not pb_fk_cs.name in normalized_custom_shapes:
                                for v in pb_fk_cs.data.vertices:
                                    v.co *= get_custom_shape_scale(pb_fk)
                                normalized_custom_shapes.append(pb_fk_cs.name)

                        dr_fk = rig.driver_add(dr_dp_fk, idx)
                        dr_fk.driver.expression = 'var'
                        base_var = dr_fk.driver.variables.new()
                        base_var.type = 'SINGLE_PROP'
                        base_var.name = 'var'
                        base_var.targets[0].id = rig
                        base_var.targets[0].data_path = 'pose.bones["' + root_finger_name + '"].["ik_fk_switch"]'


            elif type == "ik_target" or type == "ik_target2" or type == "ik_pole":
                # set custom shape
                # only set shape if it hasn't been already edited by user
                set_shape = False
                if pb_ik.custom_shape == None:
                    set_shape = True
                elif not pb_ik.custom_shape.name.startswith("cs_user_"):
                    set_shape = True

                if set_shape:
                    cs_obj_name = ""

                    if type == "ik_pole":
                        cs_obj_name = fingers_ik_pole_shape
                    elif type == "ik_target":
                        cs_obj_name = fingers_ik_shape
                    elif type == "ik_target2":
                        cs_obj_name = fingers_ik2_shape  +"_offset"

                    cs_obj = get_object(cs_obj_name)

                    if cs_obj == None:
                        append_from_arp(nodes=[cs_obj_name], type='object')
                        cs_obj = get_object(cs_obj_name)

                    pb_ik.custom_shape = cs_obj

                # IK shape scale driver
                dr_dp = 'pose.bones["'+pb_ik.name+'"].'+get_custom_shape_scale_prop_name()           
              
                
                for idx in arr_ids:
                    dr = drivers_list.find(dr_dp, index=idx)
                    if dr == None:
                        dr = rig.driver_add(dr_dp, idx)

                    if type == "ik_target":
                        dr.driver.expression = "(1-var) * (var_tip)"
                    elif type == "ik_target2":
                        dr.driver.expression = "(1-var) * (1-var_tip)"
                    elif type == "ik_pole":
                        dr.driver.expression = "1-var"

                    #   ik fk switch var
                    var_switch = dr.driver.variables.get("var")
                    if var_switch == None:
                        var_switch = dr.driver.variables.new()
                    var_switch.type = 'SINGLE_PROP'
                    var_switch.name = 'var'
                    var_switch.targets[0].id = rig
                    var_switch.targets[0].data_path = 'pose.bones["' + root_finger_name + '"].["ik_fk_switch"]'

                    #   ik tip var
                    var_tip = dr.driver.variables.get("var_tip")
                    if var_tip == None:
                        var_tip = dr.driver.variables.new()
                    var_tip.type = 'SINGLE_PROP'
                    var_tip.name = 'var_tip'
                    var_tip.targets[0].id = rig
                    var_tip.targets[0].data_path = 'pose.bones["' + root_finger_name + '"].["ik_tip"]'



            # Set color groups
            if type == "ik_chain":
                if pb_ik.bone_group == None:
                    pb_ik.bone_group = rig.pose.bone_groups.get("body"+side[-2:])

            elif type == "ik_target" or type == "ik_target2":
                grp_name = type+side[-2:]
                ik_target_grp = rig.pose.bone_groups.get(grp_name)
                if ik_target_grp == None:
                    ik_target_grp = rig.pose.bone_groups.new(name=grp_name)
                    ik_target_grp.color_set = 'CUSTOM'

                # set normal color
                if type == "ik_target":
                    grp_color = fingers_ik_color
                elif type == "ik_target2":
                    grp_color = fingers_ik2_color

                ik_target_grp.colors.normal = grp_color

                # set select color/active color
                for col_idx in range(0,3):
                    ik_target_grp.colors.select[col_idx] = grp_color[col_idx] + 0.2
                    ik_target_grp.colors.active[col_idx] = grp_color[col_idx] + 0.4

                pb_ik.bone_group = ik_target_grp

            elif type == "ik_pole":
                grp_name = "ik_pole"+side[-2:]
                ik_pole_grp = rig.pose.bone_groups.get(grp_name)
                if ik_pole_grp == None:
                    ik_pole_grp = rig.pose.bone_groups.new(name=grp_name)
                    ik_pole_grp.color_set = 'CUSTOM'

                # set normal color
                ik_pole_grp.colors.normal = fingers_ik_pole_color

                # set select color/active color
                for col_idx in range(0,3):
                    ik_pole_grp.colors.select[col_idx] = fingers_ik_pole_color[col_idx] + 0.2
                    ik_pole_grp.colors.active[col_idx] = fingers_ik_pole_color[col_idx] + 0.4

                pb_ik.bone_group = ik_pole_grp

        # Set IK constraints pole angle
        set_fingers_ik_angle(pole_angles_dict, side)

        # Edit Mode
        bpy.ops.object.mode_set(mode='EDIT')

    else:# remove fingers IK
        print("Remove Fingers IK...")

        # Pose Mode
        bpy.ops.object.mode_set(mode='POSE')

        drivers_list = rig.animation_data.drivers

        for type in ["thumb", "index", "middle", "ring", "pinky"]:
            # if finger enabled only
            phal1_ref = get_pose_bone(type+"1_ref"+side)
            if phal1_ref == None:
                continue

            ik_target_name = "c_"+type+"_ik"+side
            ik_target2_name = "c_"+type+"_ik2"+side
            ik_pole_name = "c_"+type+"_pole"+side

            # Bone chains
            for fi in range(1, 4):
                f_idx = str(fi)
                ik_name = "c_"+type+f_idx+"_ik"+side
                c_ik = get_pose_bone(ik_name)
                fk_name = ik_name.replace("_ik", "")
                c_fk = get_pose_bone(fk_name)

                # Remove FK bones IK copy constraints
                # -Copy Rotation
                    # drivers (optional, may avoid crashes)
                dr_dp = 'pose.bones["'+fk_name+'"].constraints["Rot_IK"].influence'
                dr = drivers_list.find(dr_dp)
                if dr:
                    rig.animation_data.drivers.remove(dr)

                copy_cns = c_fk.constraints.get("Rot_IK")
                if copy_cns:
                    c_fk.constraints.remove(copy_cns)

                # -Copy Scale
                    # drivers (optional, may avoid crashes)
                dr_dp = 'pose.bones["'+fk_name+'"].constraints["Scale_IK"].influence'
                dr = drivers_list.find(dr_dp)
                if dr:
                    rig.animation_data.drivers.remove(dr)

                copy_cns = c_fk.constraints.get("Scale_IK")
                if copy_cns:
                    c_fk.constraints.remove(copy_cns)

                # Remove FK shape scale driver (optional, may avoid crashes)
                dr_dp_fk = 'pose.bones["'+fk_name+'"].'+get_custom_shape_scale_prop_name()
                    
                for idx in arr_ids:
                    dr_fk = drivers_list.find(dr_dp_fk, index=idx)
                    if dr_fk:
                        rig.animation_data.drivers.remove(dr_fk)
                    
                set_custom_shape_scale(c_fk, 1.0)# reset shape scale

                # Remove IK shape scale driver (optional, may avoid crashes)
                dr_dp_fk = 'pose.bones["'+ik_name+'"].'+get_custom_shape_scale_prop_name()
                
                for idx in arr_ids:
                    dr_fk = drivers_list.find(dr_dp_fk, index=idx)
                    if dr_fk:
                        rig.animation_data.drivers.remove(dr_fk)
                
                if c_ik:
                    set_custom_shape_scale(c_ik, 1.0)# reset shape scale

            root_finger_name = "c_"+type+"1_base"+side
            root_finger = get_pose_bone(root_finger_name)

            # IK shape scale driver (optional, may avoid crashes)
            for ik_t_name in [ik_target_name, ik_target2_name]:
                dr_dp = 'pose.bones["'+ik_t_name+'"].'+get_custom_shape_scale_prop_name()
                
                for idx in arr_ids:
                    dr = drivers_list.find(dr_dp, index=idx)
                    if dr:
                        rig.animation_data.drivers.remove(dr)

            # remove properties
            for pname in ["ik_fk_switch", "ik_tip"]:
                if pname in root_finger.keys():
                    del root_finger[pname]

        # Edit Mode
        bpy.ops.object.mode_set(mode='EDIT')

        # Remove IK bones
        for fing_type in ["thumb", "index", "middle", "ring", "pinky"]:
            # if finger enabled only
            phal1_ref = get_edit_bone(fing_type+"1_ref"+side)
            if phal1_ref == None:
                continue

            # Remove IK bones
            ik_target_name = "c_"+fing_type+"_ik"+side
            ik_target2_name = "c_"+fing_type+"_ik2"+side
            ik_pole_name = "c_"+fing_type+"_pole"+side

            # IK chain
            for fi in range(1, 4):
                f_idx = str(fi)
                ref_name = fing_type+f_idx+"_ref"+side
                ref_bone = get_edit_bone(ref_name)
                c_ik_name = "c_"+fing_type+f_idx+"_ik"+side
                c_ik = get_edit_bone(c_ik_name)
                if c_ik :
                    delete_edit_bone(c_ik)

            # IK target 1 (tip)
            c_ik_target = get_edit_bone(ik_target_name)
            if c_ik_target:
                delete_edit_bone(c_ik_target)

            # IK target 2 (tip)
            c_ik_target2 = get_edit_bone(ik_target2_name)
            if c_ik_target2:
                delete_edit_bone(c_ik_target2)

            # IK pole
            c_ik_pole = get_edit_bone(ik_pole_name)
            if c_ik_pole:
                delete_edit_bone(c_ik_pole)

        remove_invalid_drivers()

    # restore picker
    restore_proxy_picker(proxy_picker_state)

    # restore layers
    restore_armature_layers(layers_select)

    # end set_fingers()


def set_fingers_ik_angle(pole_angles_dict, side):
    for fing_type in pole_angles_dict:
        phal3_name = "c_"+fing_type+"3_ik"+side
        phal3 = get_pose_bone(phal3_name)
        ik_cns = phal3.constraints.get("IK")
        ik_cns.pole_angle = pole_angles_dict[fing_type]

        phal2_name = "c_"+fing_type+"2_ik"+side
        phal2 = get_pose_bone(phal2_name)
        ik_cns2 = phal2.constraints.get("IK")
        ik_cns2.pole_angle = pole_angles_dict[fing_type]


def set_arm_feathers(wings_enabled, arm_feathers_count, forearm_feathers_count, hand_feathers_count, feathers_layers,
                     feathers_subdiv, update_transforms, parent_feathers_layers, fold_controller, side):

    arm_ref = get_edit_bone("arm_ref" + side)
    if arm_ref == None:# fix when calling the function from Quick Rig
        return
    
    scn = bpy.context.scene
    
    # disable X mirror
    xmirror_state = bpy.context.object.data.use_mirror_x
    bpy.context.object.data.use_mirror_x = False

    # enable all layers
    layers_select = enable_all_armature_layers()

    # reset layers and subdiv count if wings are disabled
    if wings_enabled == False:
        feathers_layers = 1
        feathers_subdiv = 1

    # store the feathers count in a custom prop
    arm_ref["arp_feathers"] = arm_feathers_count
    arm_ref["arp_wings"] = wings_enabled
    arm_ref["arp_feathers_layers"] = feathers_layers
    arm_ref["arp_feathers_subdiv"] = feathers_subdiv
    arm_ref["arp_feathers_update"] = update_transforms
    arm_ref["arp_feathers_layers_parent"] = parent_feathers_layers
    arm_ref["arp_feathers_fold_controller"] = fold_controller
    forearm_ref = get_edit_bone("forearm_ref" + side)
    forearm_ref["arp_feathers"] = forearm_feathers_count
    hand_ref = get_edit_bone("hand_ref" + side)
    hand_ref["arp_feathers"] = hand_feathers_count

    # store the valid feather indexes to remove invalid ones later
    valid_arm_indexes = []
    valid_forearm_indexes = []
    valid_hand_indexes = []

    side_fac = 1
    if side.endswith(".r"):
        side_fac = -1

    if wings_enabled:
        # Create Bones
        print("Adding feathers...")

        controllers_created = []
        
        ### Add Ref bones
        # arm
        arm_f_ref_bones = []
        for i in range(1, arm_feathers_count + 1):
            idx = "{0:0=2d}".format(i)

            for j in range(1, feathers_layers + 1):
                layeridx = "{0:0=2d}".format(j)

                feather_ref_name = "arm_feather_" + str(idx) + "_" + layeridx + "_ref" + side
                feather_ref_arm = get_edit_bone(feather_ref_name)
                bone_already_exist = True

                if feather_ref_arm == None:
                    feather_ref_arm = create_edit_bone(feather_ref_name)
                    bone_already_exist = False

                if update_transforms or bone_already_exist == False:
                    feather_ref_arm.head = arm_ref.head + ((arm_ref.tail - arm_ref.head) / (arm_feathers_count + 1)) * i
                    fac = -1 if scn.arp_retro_axes == False else 1
                    feather_ref_arm.tail = feather_ref_arm.head + (side_fac * arm_ref.x_axis.normalized() * fac * (arm_ref.tail - arm_ref.head).magnitude)
                    feather_ref_arm.tail = feather_ref_arm.head + ((feather_ref_arm.tail - feather_ref_arm.head) / feathers_layers) * j
                    if scn.arp_retro_axes:
                        align_bone_x_axis(feather_ref_arm, arm_ref.z_axis)
                        feather_ref_arm.roll += radians(-90)
                    else:
                        align_bone_z_axis(feather_ref_arm, arm_ref.z_axis)
                    

                feather_ref_arm.use_deform = False
                # set layer
                set_bone_layer(feather_ref_arm, 17)
                # set parent
                feather_ref_arm.parent = arm_ref
                # register
                arm_f_ref_bones.append(feather_ref_arm.name)

        # forearm
        forearm_f_ref_bones = []
        for i in range(1, forearm_feathers_count + 1):
            idx = "{0:0=2d}".format(i)

            for j in range(1, feathers_layers + 1):
                layeridx = "{0:0=2d}".format(j)

                feather_ref_name = "forearm_feather_" + str(idx) + "_" + layeridx + "_ref" + side
                feather_ref_forearm = get_edit_bone(feather_ref_name)
                bone_already_exist = True

                if feather_ref_forearm == None:
                    feather_ref_forearm = create_edit_bone(feather_ref_name)
                    bone_already_exist = False

                if update_transforms or bone_already_exist == False:
                    feather_ref_forearm.head = forearm_ref.head + ((forearm_ref.tail - forearm_ref.head) / (forearm_feathers_count + 1)) * i
                    fac = -1 if scn.arp_retro_axes == False else 1
                    feather_ref_forearm.tail = feather_ref_forearm.head + (side_fac * forearm_ref.x_axis.normalized() * fac * (forearm_ref.tail - forearm_ref.head).magnitude)
                    feather_ref_forearm.tail = feather_ref_forearm.head + ((feather_ref_forearm.tail - feather_ref_forearm.head) / feathers_layers) * j
                    if scn.arp_retro_axes:
                        align_bone_x_axis(feather_ref_forearm, forearm_ref.z_axis)
                        feather_ref_forearm.roll += radians(-90)
                    else:
                        align_bone_z_axis(feather_ref_forearm, forearm_ref.z_axis)

                feather_ref_forearm.use_deform = False
                set_bone_layer(feather_ref_forearm, 17)
                feather_ref_forearm.parent = forearm_ref
                forearm_f_ref_bones.append(feather_ref_forearm.name)

        # hand
        hand_f_ref_bones = []
        for i in range(1, hand_feathers_count + 1):
            idx = "{0:0=2d}".format(i)

            for j in range(1, feathers_layers + 1):
                layeridx = "{0:0=2d}".format(j)

                feather_ref_name = "hand_feather_" + str(idx) + "_" + layeridx + "_ref" + side
                feather_ref_hand = get_edit_bone(feather_ref_name)
                bone_already_exist = True

                if feather_ref_hand == None:
                    feather_ref_hand = bpy.context.active_object.data.edit_bones.new(feather_ref_name)
                    bone_already_exist = False

                if update_transforms or bone_already_exist == False:
                    feather_ref_hand.head = hand_ref.head + ((hand_ref.tail - hand_ref.head) / (hand_feathers_count + 1)) * i
                    feather_ref_hand.tail = feather_ref_hand.head + (-hand_ref.x_axis.normalized() * side_fac * (hand_ref.tail - hand_ref.head).magnitude)
                    feather_ref_hand.tail = feather_ref_hand.head + ((feather_ref_hand.tail - feather_ref_hand.head) / feathers_layers) * j
                    align_bone_x_axis(feather_ref_hand, hand_ref.z_axis)
                    feather_ref_hand.roll += radians(90)

                feather_ref_hand.use_deform = False
                set_bone_layer(feather_ref_hand, 17)
                feather_ref_hand.parent = hand_ref
                hand_f_ref_bones.append(feather_ref_hand.name)

        # Wings fold
        if fold_controller:
            # ref
            fold_ref_name = "wings_fold_ref" + side
            fold_ref = get_edit_bone(fold_ref_name)
            if fold_ref == None:
                fold_ref = bpy.context.active_object.data.edit_bones.new(fold_ref_name)                
                
            fold_ref.head = ((arm_ref.head + arm_ref.tail) / 2) + (-arm_ref.z_axis.normalized() * (arm_ref.tail - arm_ref.head).magnitude)
            fold_ref.tail = fold_ref.head + (-arm_ref.z_axis.normalized() * (arm_ref.tail - arm_ref.head).magnitude * 0.5)

            fold_ref.parent = get_edit_bone("shoulder_ref" + side).parent
            fold_ref.use_deform = False
            set_bone_layer(fold_ref, 17)

            # control
            fold_cont_name = "c_wings_fold" + side
            fold_cont = get_edit_bone(fold_cont_name)
            if fold_cont == None:
                fold_cont = bpy.context.active_object.data.edit_bones.new(fold_cont_name)
                controllers_created.append(fold_cont_name)
                
            fold_cont.head = ((arm_ref.head + arm_ref.tail) / 2) + (-arm_ref.z_axis.normalized() * (arm_ref.tail - arm_ref.head).magnitude)
            fold_cont.tail = fold_cont.head + (-arm_ref.z_axis.normalized() * (arm_ref.tail - arm_ref.head).magnitude * 0.5)

            fold_cont.parent = get_edit_bone("c_shoulder" + side).parent
            fold_cont.use_deform = False
            set_bone_layer(fold_cont, 0)
            

        ### Add Main feather bones
        # arm
        arm_stretch = get_edit_bone("arm_stretch" + side)
        last_layer_idx = "{0:0=2d}".format(feathers_layers)

        for i in range(1, arm_feathers_count + 1):
            featheridx = "{0:0=2d}".format(i)
            feather_ref_name = "arm_feather_" + featheridx + "_" + last_layer_idx + "_ref" + side
            feather_ref = get_edit_bone(feather_ref_name)
            main_f_name = "arm_feather_" + featheridx + side
            main_f = get_edit_bone(main_f_name)

            # create control bone if necessary
            if main_f == None:
                main_f = bpy.context.active_object.data.edit_bones.new(main_f_name)

            main_f.head, main_f.tail = [0, 0, 0], [0, 1, 0]

            # set parent
            main_f.parent = arm_stretch
            set_bone_layer(main_f, 24)

            main_f.use_deform = False

        # forearm
        forearm_stretch = get_edit_bone("forearm_stretch" + side)
        for i in range(1, forearm_feathers_count + 1):
            featheridx = "{0:0=2d}".format(i)
            feather_ref_name = "forearm_feather_" + featheridx + "_" + last_layer_idx + "_ref" + side
            feather_ref = get_edit_bone(feather_ref_name)
            main_f_name = "forearm_feather_" + featheridx + side
            main_f = get_edit_bone(main_f_name)

            # create control bone if necessary
            if main_f == None:
                main_f = bpy.context.active_object.data.edit_bones.new(main_f_name)

            main_f.head, main_f.tail = [0, 0, 0], [0, 1, 0]

            # set parent
            main_f.parent = forearm_stretch
            set_bone_layer(main_f, 24)

            main_f.use_deform = False

        # hand
        hand = get_edit_bone("hand" + side)
        for i in range(1, hand_feathers_count + 1):
            featheridx = "{0:0=2d}".format(i)
            feather_ref_name = "hand_feather_" + featheridx + "_" + last_layer_idx + "_ref" + side
            feather_ref = get_edit_bone(feather_ref_name)
            main_f_name = "hand_feather_" + featheridx + side
            main_f = get_edit_bone(main_f_name)

            # create control bone if necessary
            if main_f == None:
                main_f = bpy.context.active_object.data.edit_bones.new(main_f_name)

            main_f.head, main_f.tail = [0, 0, 0], [0, 1, 0]

            # set parent
            if i != hand_feathers_count:
                main_f.parent = hand
            else:  # the last one must be parented to the master bone
                print("set master")
                hand_feather_master_name = "c_hand_feather_master" + side
                hand_feather_master = get_edit_bone(hand_feather_master_name)
                if hand_feather_master == None:
                    hand_feather_master = bpy.context.active_object.data.edit_bones.new(hand_feather_master_name)

                hand_feather_master.use_deform = False

                hand_feather_master.parent = hand
                set_bone_layer(hand_feather_master, 0)
                main_f.parent = hand_feather_master
                print(main_f.name, "parent = ", hand_feather_master.name)

            # set bone layer
            set_bone_layer(main_f, 24)

            main_f.use_deform = False

        align_feather_main(arm_feathers_count, forearm_feathers_count, hand_feathers_count, feathers_layers, side)

        ### Add Control bones
        # name format: c_limb_feather_featherindex_layerindex_subdivindex + side
        feather_controllers = []

        # arm
        arm_stretch = get_edit_bone("arm_stretch" + side)

        for i_f in range(1, arm_feathers_count + 1):
            featheridx = "{0:0=2d}".format(i_f)

            for i_layer in range(1, feathers_layers + 1):
                layeridx = "{0:0=2d}".format(i_layer)

                for i_subdiv in range(1, feathers_subdiv + 1):
                    subdividx = "{0:0=2d}".format(i_subdiv)
                    c_bone_name = "c_arm_feather_" + featheridx + '_' + layeridx + '_' + subdividx + side
                    c_bone = get_edit_bone(c_bone_name)
                    # create control bone if necessary
                    if c_bone == None:
                        c_bone = bpy.context.active_object.data.edit_bones.new(c_bone_name)
                        controllers_created.append(c_bone_name)

                    c_bone.head, c_bone.tail = [0, 0, 0], [0, 1, 0]

                    # register it
                    feather_controllers.append(c_bone.name)
                    valid_arm_indexes.append(featheridx + '_' + layeridx + '_' + subdividx + side)

                    # set parent
                    if i_layer == 1 and i_subdiv == 1:
                        # parent to main feather
                        c_bone.parent = get_edit_bone("arm_feather_" + featheridx + side)
                    elif i_layer != 1 and i_subdiv == 1:
                        if parent_feathers_layers:
                            # parent to previous layer
                            prev_layer_idx = "{0:0=2d}".format(i_layer - 1)
                            c_bone_name_prev = "c_arm_feather_" + featheridx + '_' + prev_layer_idx + '_' + subdividx + side
                            c_bone.parent = get_edit_bone(c_bone_name_prev)
                        else:
                            # parent to main feather
                            c_bone.parent = get_edit_bone("arm_feather_" + featheridx + side)
                    else:
                        # parent to previous subdiv
                        prev_subdiv_idx = "{0:0=2d}".format(i_subdiv - 1)
                        c_bone_name_prev = "c_arm_feather_" + featheridx + '_' + layeridx + '_' + prev_subdiv_idx + side
                        c_bone.parent = get_edit_bone(c_bone_name_prev)

                    # set layers
                    set_bone_layer(c_bone, 0)

        # forearm
        for i_f in range(1, forearm_feathers_count + 1):
            featheridx = "{0:0=2d}".format(i_f)

            for i_layer in range(1, feathers_layers + 1):
                layeridx = "{0:0=2d}".format(i_layer)

                for i_subdiv in range(1, feathers_subdiv + 1):
                    subdividx = "{0:0=2d}".format(i_subdiv)
                    c_bone_name = "c_forearm_feather_" + featheridx + '_' + layeridx + '_' + subdividx + side
                    c_bone = get_edit_bone(c_bone_name)

                    # create bone if necessary
                    if c_bone == None:
                        c_bone = bpy.context.active_object.data.edit_bones.new(c_bone_name)
                        controllers_created.append(c_bone_name)

                    c_bone.head, c_bone.tail = [0, 0, 0], [0, 1, 0]

                    # register it
                    feather_controllers.append(c_bone.name)
                    valid_forearm_indexes.append(featheridx + '_' + layeridx + '_' + subdividx + side)

                    # set parent
                    if i_layer == 1 and i_subdiv == 1:
                        # parent to main feather
                        c_bone.parent = get_edit_bone("forearm_feather_" + featheridx + side)
                    elif i_layer != 1 and i_subdiv == 1:
                        if parent_feathers_layers:
                            # parent to previous layer
                            prev_layer_idx = "{0:0=2d}".format(i_layer - 1)
                            c_bone_name_prev = "c_forearm_feather_" + featheridx + '_' + prev_layer_idx + '_' + subdividx + side
                            c_bone.parent = get_edit_bone(c_bone_name_prev)
                        else:
                            # parent to main feather
                            c_bone.parent = get_edit_bone("forearm_feather_" + featheridx + side)
                    else:
                        # parent to previous subdiv
                        prev_subdiv_idx = "{0:0=2d}".format(i_subdiv - 1)
                        c_bone_name_prev = "c_forearm_feather_" + featheridx + '_' + layeridx + '_' + prev_subdiv_idx + side
                        c_bone.parent = get_edit_bone(c_bone_name_prev)

                    # set layer
                    set_bone_layer(c_bone, 0)

        # hand
        for i_f in range(1, hand_feathers_count + 1):
            featheridx = "{0:0=2d}".format(i_f)

            for i_layer in range(1, feathers_layers + 1):
                layeridx = "{0:0=2d}".format(i_layer)

                for i_subdiv in range(1, feathers_subdiv + 1):
                    subdividx = "{0:0=2d}".format(i_subdiv)
                    c_bone_name = "c_hand_feather_" + featheridx + '_' + layeridx + '_' + subdividx + side
                    c_bone = get_edit_bone(c_bone_name)
                    # create control bone if necessary
                    if c_bone == None:
                        c_bone = bpy.context.active_object.data.edit_bones.new(c_bone_name)
                        controllers_created.append(c_bone_name)

                    c_bone.head, c_bone.tail = [0, 0, 0], [0, 1, 0]

                    # register it
                    feather_controllers.append(c_bone.name)
                    valid_hand_indexes.append(featheridx + '_' + layeridx + '_' + subdividx + side)

                    # set parent
                    if i_layer == 1 and i_subdiv == 1:
                        # parent to main feather
                        c_bone.parent = get_edit_bone("hand_feather_" + featheridx + side)
                    elif i_layer != 1 and i_subdiv == 1:
                        if parent_feathers_layers:
                            # parent to previous layer
                            prev_layer_idx = "{0:0=2d}".format(i_layer - 1)
                            c_bone_name_prev = "c_hand_feather_" + featheridx + '_' + prev_layer_idx + '_' + subdividx + side
                            c_bone.parent = get_edit_bone(c_bone_name_prev)
                        else:
                            # parent to main feather
                            c_bone.parent = get_edit_bone("hand_feather_" + featheridx + side)
                    else:
                        # parent to previous subdiv
                        prev_subdiv_idx = "{0:0=2d}".format(i_subdiv - 1)
                        c_bone_name_prev = "c_hand_feather_" + featheridx + '_' + layeridx + '_' + prev_subdiv_idx + side
                        c_bone.parent = get_edit_bone(c_bone_name_prev)

                    # set layers
                    set_bone_layer(c_bone, 0)

        align_feather_controls(arm_feathers_count, forearm_feathers_count, hand_feathers_count, feathers_layers,
                               feathers_subdiv, side)

        ### Add Mid Control Bones
        # arm
        c_arm_mid_name = "c_arm_feather_mid" + side
        c_arm_feather_mid = get_edit_bone(c_arm_mid_name)
        # create bone if necessary
        if c_arm_feather_mid == None:
            c_arm_feather_mid = bpy.context.active_object.data.edit_bones.new(c_arm_mid_name)
            controllers_created.append(c_arm_mid_name)

        c_arm_feather_mid.head, c_arm_feather_mid.tail = [0, 0, 0], [0, 1, 0]
        c_arm_feather_mid.parent = arm_stretch
        c_arm_feather_mid.use_deform = False
        set_bone_layer(c_arm_feather_mid, 0)

        # forearm
        c_forearm_mid_name = "c_forearm_feather_mid" + side
        c_forearm_feather_mid = get_edit_bone(c_forearm_mid_name)
        # create bone if necessary
        if c_forearm_feather_mid == None:
            c_forearm_feather_mid = bpy.context.active_object.data.edit_bones.new(c_forearm_mid_name)
            controllers_created.append(c_forearm_mid_name)

        c_forearm_feather_mid.head, c_forearm_feather_mid.tail = [0, 0, 0], [0, 1, 0]
        c_forearm_feather_mid.parent = forearm_stretch
        c_forearm_feather_mid.use_deform = False
        set_bone_layer(c_forearm_feather_mid, 0)

        # hand
        c_hand_mid_name = "c_hand_feather_mid" + side
        c_hand_feather_mid = get_edit_bone(c_hand_mid_name)
        # create bone if necessary
        if c_hand_feather_mid == None:
            c_hand_feather_mid = bpy.context.active_object.data.edit_bones.new(c_hand_mid_name)
            controllers_created.append(c_hand_mid_name)

        c_hand_feather_mid.head, c_hand_feather_mid.tail = [0, 0, 0], [0, 1, 0]
        c_hand_feather_mid.parent = hand
        c_hand_feather_mid.use_deform = False
        set_bone_layer(c_hand_feather_mid, 0)

        # align
        first_arm_feather = get_edit_bone(arm_f_ref_bones[feathers_layers - 1])
        last_arm_feather = get_edit_bone(arm_f_ref_bones[len(arm_f_ref_bones) - 1])
        first_forearm_feather = get_edit_bone(forearm_f_ref_bones[feathers_layers - 1])
        last_forearm_feather = get_edit_bone(forearm_f_ref_bones[len(forearm_f_ref_bones) - 1])
        first_hand_feather = get_edit_bone(hand_f_ref_bones[feathers_layers - 1])
        last_hand_feather = get_edit_bone(hand_f_ref_bones[len(hand_f_ref_bones) - 1])

        align_feather_mid(first_arm_feather, last_arm_feather, first_forearm_feather, last_forearm_feather,
                          first_hand_feather, side)

        ### Add Mid Targets
        # arm
        arm_feather_mid_target_name = "arm_feather_mid_target" + side
        arm_feather_mid_target = get_edit_bone(arm_feather_mid_target_name)
        if arm_feather_mid_target == None:
            arm_feather_mid_target = bpy.context.active_object.data.edit_bones.new(arm_feather_mid_target_name)            

        arm_feather_mid_target.parent = get_edit_bone("shoulder" + side)

        arm_feather_mid_target.use_deform = False

        set_bone_layer(arm_feather_mid_target, 8)

        # forearm
        forearm_feather_mid_target_name = "forearm_feather_mid_target" + side
        forearm_feather_mid_target = get_edit_bone(forearm_feather_mid_target_name)
        if forearm_feather_mid_target == None:
            forearm_feather_mid_target = bpy.context.active_object.data.edit_bones.new(forearm_feather_mid_target_name)

        forearm_feather_mid_target.parent = arm_stretch

        forearm_feather_mid_target.use_deform = False

        set_bone_layer(forearm_feather_mid_target, 8)

        # hand
        hand_feather_mid_target_name = "hand_feather_mid_target" + side
        hand_feather_mid_target = get_edit_bone(hand_feather_mid_target_name)
        if hand_feather_mid_target == None:
            hand_feather_mid_target = bpy.context.active_object.data.edit_bones.new(hand_feather_mid_target_name)

        hand_feather_mid_target.parent = forearm_stretch

        hand_feather_mid_target.use_deform = False

        set_bone_layer(hand_feather_mid_target, 8)

        # align
        align_feather_mid_targets(side)

        ###  Add Stretches
        # arms
        arm_f_stretch_name = "arm_feather_stretch" + side
        arm_f_stretch = get_edit_bone(arm_f_stretch_name)
        # create if necessary
        if arm_f_stretch == None:
            arm_f_stretch = bpy.context.active_object.data.edit_bones.new(arm_f_stretch_name)
        arm_f_stretch.head, arm_f_stretch.tail = [0.0, 0.0, 0.0], [1.0, 0.0, 0.0]

        arm_f_stretch.parent = c_arm_feather_mid

        arm_f_stretch.use_deform = False

        set_bone_layer(arm_f_stretch, 24)

        # forearms
        forearm_f_stretch_name = "forearm_feather_stretch" + side
        forearm_f_stretch = get_edit_bone(forearm_f_stretch_name)
        # create if necessary
        if forearm_f_stretch == None:
            forearm_f_stretch = bpy.context.active_object.data.edit_bones.new(forearm_f_stretch_name)
        forearm_f_stretch.head, forearm_f_stretch.tail = [0.0, 0.0, 0.0], [1.0, 0.0, 0.0]

        forearm_f_stretch.parent = c_forearm_feather_mid

        forearm_f_stretch.use_deform = False

        set_bone_layer(forearm_f_stretch, 24)

        # hands
        hand_f_stretch_name = "hand_feather_stretch" + side
        hand_f_stretch = get_edit_bone(hand_f_stretch_name)
        # create if necessary
        if hand_f_stretch == None:
            hand_f_stretch = bpy.context.active_object.data.edit_bones.new(hand_f_stretch_name)
        hand_f_stretch.head, hand_f_stretch.tail = [0.0, 0.0, 0.0], [1.0, 0.0, 0.0]

        hand_f_stretch.parent = c_hand_feather_mid

        hand_f_stretch.use_deform = False

        set_bone_layer(hand_f_stretch, 24)

        # align
        align_feather_stretches(last_hand_feather, side)

        ### Add Targets
        layers_last_idx = "{0:0=2d}".format(feathers_layers)
        arm_feather_stretch = get_edit_bone("arm_feather_stretch" + side)

        #   arms
        for i in range(1, arm_feathers_count + 1):
            featheridx = "{0:0=2d}".format(i)
            feather_ref_name = "arm_feather_" + featheridx + "_" + layers_last_idx + "_ref" + side
            feather_ref = get_edit_bone(feather_ref_name)
            arm_f_target_name = "arm_feather_target_" + featheridx + side
            arm_f_target = get_edit_bone(arm_f_target_name)

            if arm_f_target == None:
                arm_f_target = bpy.context.active_object.data.edit_bones.new(arm_f_target_name)

            arm_f_target.head, arm_f_target.tail = [0, 0, 0], [0, 1, 0]

            arm_f_target.parent = arm_feather_stretch
            arm_f_target.use_deform = False
            set_bone_layer(arm_f_target, 24)

        #   forearms
        for i in range(1, forearm_feathers_count + 1):
            featheridx = "{0:0=2d}".format(i)
            feather_ref_name = "forearm_feather_" + featheridx + "_" + layers_last_idx + "_ref" + side
            feather_ref = get_edit_bone(feather_ref_name)
            forearm_f_target_name = "forearm_feather_target_" + featheridx + side
            forearm_f_target = get_edit_bone(forearm_f_target_name)

            if forearm_f_target == None:
                forearm_f_target = bpy.context.active_object.data.edit_bones.new(forearm_f_target_name)

            forearm_f_target.head, forearm_f_target.tail = [0, 0, 0], [0, 1, 0]

            forearm_f_target.parent = get_edit_bone("forearm_feather_stretch" + side)
            forearm_f_target.use_deform = False
            set_bone_layer(forearm_f_target, 24)

        #   hands
        for i in range(1, hand_feathers_count):
            featheridx = "{0:0=2d}".format(i)
            feather_ref_name = "hand_feather_" + featheridx + "_" + layers_last_idx + "_ref" + side
            feather_ref = get_edit_bone(feather_ref_name)
            hand_f_target_name = "hand_feather_target_" + featheridx + side
            hand_f_target = get_edit_bone(hand_f_target_name)

            if hand_f_target == None:
                hand_f_target = bpy.context.active_object.data.edit_bones.new(hand_f_target_name)

            hand_f_target.head, hand_f_target.tail = [0, 0, 0], [0, 1, 0]

            hand_f_target.parent = get_edit_bone("hand_feather_stretch" + side)
            hand_f_target.use_deform = False
            set_bone_layer(hand_f_target, 24)

        # align
        align_feather_targets(arm_feathers_count, forearm_feathers_count, hand_feathers_count, side)

        ### Add constraints
        bpy.ops.object.mode_set(mode='POSE')

        ## Mids
        # arm
        c_arm_f_mid_name = "c_arm_feather_mid" + side
        c_arm_f_mid = get_pose_bone(c_arm_f_mid_name)

        cns = c_arm_f_mid.constraints.get("Copy Rotation")
        if cns == None:
            cns = c_arm_f_mid.constraints.new("COPY_ROTATION")
            cns.influence = 1.0
        cns.target = bpy.context.active_object
        cns.subtarget = "arm_feather_mid_target" + side

        # forearm
        c_forearm_f_mid_name = "c_forearm_feather_mid" + side
        c_forearm_f_mid = get_pose_bone(c_forearm_f_mid_name)

        cns = c_forearm_f_mid.constraints.get("Copy Rotation")
        if cns == None:
            cns = c_forearm_f_mid.constraints.new("COPY_ROTATION")
            cns.influence = 0.5
        cns.target = bpy.context.active_object
        cns.subtarget = "forearm_feather_mid_target" + side

        # hand
        c_hand_f_mid_name = "c_hand_feather_mid" + side
        c_hand_f_mid = get_pose_bone(c_hand_f_mid_name)

        cns = c_hand_f_mid.constraints.get("Copy Rotation")
        if cns == None:
            cns = c_hand_f_mid.constraints.new("COPY_ROTATION")
            cns.influence = 0.5
        cns.name = "Copy Rotation"
        cns.target = bpy.context.active_object
        cns.subtarget = "hand_feather_mid_target" + side

        ## Tracks
        # arm
        for i in range(1, arm_feathers_count + 1):
            idx = "{0:0=2d}".format(i)
            feather_name = "arm_feather_" + str(idx) + side
            feather_pbone = get_pose_bone(feather_name)

            cns = feather_pbone.constraints.get("Damped Track")
            if cns == None:
                cns = feather_pbone.constraints.new("DAMPED_TRACK")
            cns.name = "Damped Track"
            cns.target = bpy.context.active_object
            cns.subtarget = "arm_feather_target_" + str(idx) + side

        # forearm
        for i in range(1, forearm_feathers_count + 1):
            idx = "{0:0=2d}".format(i)
            feather_name = "forearm_feather_" + str(idx) + side
            feather_pbone = get_pose_bone(feather_name)

            cns = feather_pbone.constraints.get("Damped Track")
            if cns == None:
                cns = feather_pbone.constraints.new("DAMPED_TRACK")
            cns.name = "Damped Track"
            cns.target = bpy.context.active_object
            cns.subtarget = "forearm_feather_target_" + str(idx) + side

        # hand
        for i in range(1, hand_feathers_count + 1):
            idx = "{0:0=2d}".format(i)
            feather_name = "hand_feather_" + str(idx) + side
            feather_pbone = get_pose_bone(feather_name)

            cns = feather_pbone.constraints.get("Damped Track")
            if i != hand_feathers_count:
                if cns == None:
                    cns = feather_pbone.constraints.new("DAMPED_TRACK")
                cns.name = "Damped Track"
                cns.target = bpy.context.active_object
                cns.subtarget = "hand_feather_target_" + str(idx) + side
            else:# make sure to remove the last bone constraint
                if cns:
                    feather_pbone.constraints.remove(cns)

        ## Stretches
        # arm
        arm_f_stretch = get_pose_bone("arm_feather_stretch" + side)

        cns = arm_f_stretch.constraints.get("Stretch To")
        if cns == None:
            cns = arm_f_stretch.constraints.new("STRETCH_TO")
        cns.target = bpy.context.active_object
        cns.subtarget = "c_forearm_feather_mid" + side
        cns.head_tail = 1.0
        cns.volume = "NO_VOLUME"

        # forearm
        forearm_f_stretch = get_pose_bone("forearm_feather_stretch" + side)

        cns = forearm_f_stretch.constraints.get("Stretch To")
        if cns == None:
            cns = forearm_f_stretch.constraints.new("STRETCH_TO")
        cns.target = bpy.context.active_object
        cns.subtarget = "c_hand_feather_mid" + side
        cns.head_tail = 1.0
        cns.volume = "NO_VOLUME"

        # hand
        hand_f_stretch = get_pose_bone("hand_feather_stretch" + side)
        last_hand_feather_name = hand_f_ref_bones[len(hand_f_ref_bones) - 1]

        cns = hand_f_stretch.constraints.get("Stretch To")
        if cns == None:
            cns = hand_f_stretch.constraints.new("STRETCH_TO")
        cns.target = bpy.context.active_object

        trim = 5  # e.g hand_feather_02_03_ref.l
        to_replace = "_ref"
        if "_dupli_" in last_hand_feather_name:
            trim = 14  # e.g hand_feather_04_01_ref_dupli_001.l
            to_replace = "_ref_"
        # hand_feather_02_03.l
        # hand_feather_04_01dupli_001.l
        cns.subtarget = last_hand_feather_name.replace(to_replace, "")[:-trim] + side
        cns.head_tail = 1.0
        cns.volume = "NO_VOLUME"
        
        # Set rotations to euler
        for bname in controllers_created:
            pb = get_pose_bone(bname)
            pb.rotation_mode = 'XYZ'
        
        ### Set custom shapes, groups, Euler rotations
        group_feathers = bpy.context.active_object.pose.bone_groups.get("feathers")

        if group_feathers == None:
            group_feathers = bpy.context.active_object.pose.bone_groups.new(name="feathers")
            group_feathers.colors.normal = [0.04, 0.83, 0.0]
            group_feathers.colors.select = [0.6, 0.97, 0.6]
            group_feathers.colors.active = [0.757, 0.980, 0.776]
            group_feathers.color_set = 'CUSTOM'

        # wings fold
        if fold_controller:
            fold_cont_name = "c_wings_fold" + side
            fold_cont = get_pose_bone(fold_cont_name)
            if fold_cont.custom_shape == None:
                cs_wavy_circle = get_object("cs_wavy_circle")
                if cs_wavy_circle == None:
                    append_from_arp(nodes=["cs_wavy_circle"], type="object")
                fold_cont.custom_shape = bpy.data.objects.get("cs_wavy_circle")

            # set transforms locks
            for i in range(0, 3):
                fold_cont.lock_location[i] = True
                fold_cont.lock_rotation[i] = True

            # set group
            fold_cont.bone_group = group_feathers            
            
            # hand feather master
        hand_feather_master_name = "c_hand_feather_master" + side
        hand_feather_master = get_pose_bone(hand_feather_master_name)
        
        # only set custom shapes if not already set
        if hand_feather_master.custom_shape == None:
            hand_feather_master.custom_shape = get_object("cs_sphere")
            set_custom_shape_scale(hand_feather_master, 0.2)

        hand_feather_master.bone_group = group_feathers

        # feather controllers
        for bname in feather_controllers:
            c_bone = get_pose_bone(bname)
            # only set custom shapes if not already set
            if c_bone.custom_shape == None:
                cs_torus_tip_01 = get_object("cs_torus_tip_01")
                if cs_torus_tip_01 == None:
                    append_from_arp(nodes=["cs_torus_tip_01"], type="object")
                c_bone.custom_shape = get_object("cs_torus_tip_01")

            c_bone.bone_group = group_feathers

        # mid controllers
        c_hand_feather_mid = get_pose_bone("c_hand_feather_mid" + side)
        if c_hand_feather_mid.custom_shape == None:
            c_hand_feather_mid.custom_shape = get_object("cs_sphere")
        set_custom_shape_scale(c_hand_feather_mid, 0.3)

        c_hand_feather_mid.bone_group = group_feathers

        c_forearm_feather_mid = get_pose_bone("c_forearm_feather_mid" + side)
        if c_forearm_feather_mid.custom_shape == None:
            c_forearm_feather_mid.custom_shape = get_object("cs_sphere")
        set_custom_shape_scale(c_forearm_feather_mid, 0.3)

        c_forearm_feather_mid.bone_group = group_feathers

        c_arm_feather_mid = get_pose_bone("c_arm_feather_mid" + side)
        if c_arm_feather_mid.custom_shape == None:
            c_arm_feather_mid.custom_shape = get_object("cs_sphere")
        set_custom_shape_scale(c_arm_feather_mid, 0.3)

        c_arm_feather_mid.bone_group = group_feathers
        

    ### Delete unused bones
    bpy.ops.object.mode_set(mode='EDIT')

    # Wings fold
    if wings_enabled == False or fold_controller == False:
        # remove actions constraints
        # get all feathers controllers

        bpy.ops.object.mode_set(mode='POSE')

        arm_controllers = ["c_shoulder" + side, "c_arm_fk" + side, "c_forearm_fk" + side, "c_hand_fk" + side]
        feather_controllers = get_feather_controllers(side)

        # remove constraints
        for fc_name in feather_controllers + arm_controllers:
            fc = get_pose_bone(fc_name)
            if len(fc.constraints) > 0:
                action_cns = fc.constraints.get("Action")
                if action_cns:
                    fc.constraints.remove(action_cns)

        bpy.ops.object.mode_set(mode='EDIT')

        # reference
        fold_ref_name = "wings_fold_ref" + side
        fold_ref = get_edit_bone(fold_ref_name)
        if fold_ref:
            delete_edit_bone(fold_ref)

            # controller
        fold_cont_name = "c_wings_fold" + side
        fold_cont = get_edit_bone(fold_cont_name)
        if fold_cont:
            delete_edit_bone(fold_cont)

    max_limit = 32

    ## Main feather bones
    # arms
    if wings_enabled == False:
        start_range = 1
    else:
        start_range = arm_feathers_count + 1

    for i in range(start_range, max_limit + 1):
        idx = "{0:0=2d}".format(i)

        f_bone = get_edit_bone("arm_feather_" + idx + side)
        if f_bone:
            delete_edit_bone(f_bone)

    # forearms
    if wings_enabled == False:
        start_range = 1
    else:
        start_range = forearm_feathers_count + 1

    for i in range(start_range, max_limit + 1):
        idx = "{0:0=2d}".format(i)
        f_bone = get_edit_bone("forearm_feather_" + idx + side)

        if f_bone:
            delete_edit_bone(f_bone)

    # hands
    if wings_enabled == False:
        start_range = 1
    else:
        start_range = hand_feathers_count + 1

    for i in range(start_range, max_limit + 1):
        idx = "{0:0=2d}".format(i)
        f_bone = get_edit_bone("hand_feather_" + idx + side)

        if f_bone:
            delete_edit_bone(f_bone)

    ## Control
    if wings_enabled == False:
        # hand master
        feather_hand_master = get_edit_bone("c_hand_feather_master" + side)
        if feather_hand_master:
            delete_edit_bone(feather_hand_master)

        # mids
        c_arm_feather_mid = get_edit_bone("c_arm_feather_mid" + side)
        if c_arm_feather_mid:
            delete_edit_bone(c_arm_feather_mid)

        c_forearm_feather_mid = get_edit_bone("c_forearm_feather_mid" + side)
        if c_forearm_feather_mid:
            delete_edit_bone(c_forearm_feather_mid)

        c_hand_feather_mid = get_edit_bone("c_hand_feather_mid" + side)
        if c_hand_feather_mid:
            delete_edit_bone(c_hand_feather_mid)

    for i in bpy.context.active_object.data.edit_bones:
        # only affects current side
        _side = i.name[-2:]
        if '_dupli_' in i.name:
            _side = i.name[-12:]

        if _side != side:
            continue

        # del controls
        if i.name.startswith('c_arm_feather_') or i.name.startswith('c_forearm_feather_') or i.name.startswith(
                'c_hand_feather_'):
            if 'feather_mid' in i.name:
                continue
            if 'feather_master' in i.name:
                continue

            if i.name.startswith('c_arm_feather_'):
                f_indexes = i.name[14:]  # 01_01_01.l
                if f_indexes not in valid_arm_indexes:
                    delete_edit_bone(i)

            elif i.name.startswith('c_forearm_feather_'):
                f_indexes = i.name[18:]
                if f_indexes not in valid_forearm_indexes:
                    delete_edit_bone(i)

            elif i.name.startswith('c_hand_feather_'):
                f_indexes = i.name[15:]
                if f_indexes not in valid_hand_indexes:
                    delete_edit_bone(i)

        # and reference bones
        elif i.name.startswith("arm_feather_") and "_ref" in i.name:
            f_indexes = i.name[12:]  # 01_01_ref.l
            two_indexes = f_indexes[:5]
            valid_index = False
            for vi in valid_arm_indexes:
                if vi.startswith(two_indexes):
                    valid_index = True
                    break

            if not valid_index:
                delete_edit_bone(i)

        elif i.name.startswith("forearm_feather_") and "_ref" in i.name:
            f_indexes = i.name[16:]  # 01_01_ref.l
            two_indexes = f_indexes[:5]
            valid_index = False
            for vi in valid_forearm_indexes:
                if vi.startswith(two_indexes):
                    valid_index = True
                    break

            if not valid_index:
                delete_edit_bone(i)

        elif i.name.startswith("hand_feather_") and "_ref" in i.name:
            f_indexes = i.name[13:]  # 01_01_ref.l
            two_indexes = f_indexes[:5]
            valid_index = False
            for vi in valid_hand_indexes:
                if vi.startswith(two_indexes):
                    valid_index = True
                    break

            if not valid_index:
                delete_edit_bone(i)

    ## Targets
    # arms
    if wings_enabled == False:
        amount = 1
    else:
        amount = arm_feathers_count + 1

    for i in range(amount, max_limit + 1):
        idx = "{0:0=2d}".format(i)
        feather_ref_name = "arm_feather_target_" + str(idx) + side
        ebone = get_edit_bone(feather_ref_name)
        if ebone:
            delete_edit_bone(ebone)

    # forearms
    if wings_enabled == False:
        amount = 1
    else:
        amount = forearm_feathers_count + 1

    for i in range(amount, max_limit + 1):
        idx = "{0:0=2d}".format(i)
        feather_ref_name = "forearm_feather_target_" + str(idx) + side
        ebone = get_edit_bone(feather_ref_name)
        if ebone:
            delete_edit_bone(ebone)

    # hands
    if wings_enabled == False:
        amount = 1
    else:
        amount = hand_feathers_count + 1

    for i in range(amount - 1, max_limit + 1):  # -1 because the last bones are not used
        idx = "{0:0=2d}".format(i)
        feather_ref_name = "hand_feather_target_" + str(idx) + side
        ebone = get_edit_bone(feather_ref_name)
        if ebone:
            delete_edit_bone(ebone)

    ## Mid targets
    if wings_enabled == False:
        # arm
        arm_mid_target = get_edit_bone("arm_feather_mid_target" + side)
        if arm_mid_target:
            delete_edit_bone(arm_mid_target)

        c_arm_mid_target = get_edit_bone("c_arm_feather_mid_target" + side)
        if c_arm_mid_target:
            delete_edit_bone(c_arm_mid_target)

        # forearm
        forearm_mid_target = get_edit_bone("forearm_feather_mid_target" + side)
        if forearm_mid_target:
            delete_edit_bone(forearm_mid_target)

        c_forearm_mid_target = get_edit_bone("c_forearm_feather_mid_target" + side)
        if c_forearm_mid_target:
            delete_edit_bone(c_forearm_mid_target)

        # hand
        hand_mid_target = get_edit_bone("hand_feather_mid_target" + side)
        if hand_mid_target:
            delete_edit_bone(hand_mid_target)

        c_hand_mid_target = get_edit_bone("c_hand_feather_mid_target" + side)
        if c_hand_mid_target:
            delete_edit_bone(c_hand_mid_target)

        ## Stretches
        arm_feather_stretch = get_edit_bone("arm_feather_stretch" + side)
        if arm_feather_stretch:
            delete_edit_bone(arm_feather_stretch)

        forearm_feather_stretch = get_edit_bone("forearm_feather_stretch" + side)
        if forearm_feather_stretch:
            delete_edit_bone(forearm_feather_stretch)

        hand_feather_stretch = get_edit_bone("hand_feather_stretch" + side)
        if hand_feather_stretch:
            delete_edit_bone(hand_feather_stretch)

    # restore X mirror
    bpy.context.object.data.use_mirror_x = xmirror_state

    # restore layers
    restore_armature_layers(layers_select)

    # print("Feathers created.")


def set_arm_twist(twist_bones_amount, side, bbones_ease_out=None):
    bpy.ops.object.mode_set(mode='EDIT')

    # disable X mirror
    xmirror_state = bpy.context.object.data.use_mirror_x
    bpy.context.object.data.use_mirror_x = False

    arm_ref = get_edit_bone("arm_ref" + side)
    forearm_ref = get_edit_bone("forearm_ref" + side)

    if arm_ref == None or forearm_ref == None:
        print("arm_ref or forearm_ref not found, can't set arm twist")
        return

    secondary_are_bendy_bones = False

    if bpy.context.object.arp_secondary_type == "BENDY_BONES":
        secondary_are_bendy_bones = True
        if bbones_ease_out != None:
            arm_ref["arp_bbones_ease_out"] = bbones_ease_out

    # store the twist bones amount in a bone property to "save" it for later access
    arm_ref["twist_bones_amount"] = twist_bones_amount

    # Bones generation
    def set_limb_twist(bone_name, end_bone_name):
        bpy.ops.object.mode_set(mode='EDIT')
        stretch_bone = get_edit_bone(bone_name + '_stretch' + side)
        twist_bone = get_edit_bone(bone_name + '_twist' + side)
        end_bone = get_edit_bone(end_bone_name + side)
        c_arm_twist_offset = get_edit_bone("c_arm_twist_offset" + side)

        vec = None
        if bone_name == "forearm":
            vec = end_bone.head - stretch_bone.head
        elif bone_name == "arm":
            vec = end_bone.head - twist_bone.head

        dist_vec = vec / (twist_bones_amount + 1)

        if twist_bones_amount == 1:  # one twist bone case, position the bone halfway
            if bone_name == "forearm":
                twist_bone.head = stretch_bone.head + (vec / 2)
                twist_bone.tail = end_bone.head
                if not secondary_are_bendy_bones:
                    stretch_bone.tail = twist_bone.head
            elif bone_name == "arm":
                twist_bone.tail = end_bone.head - (vec / 2)
                c_arm_twist_offset.tail = twist_bone.tail
                if not secondary_are_bendy_bones:
                    stretch_bone.head = twist_bone.tail
                    stretch_bone.tail = end_bone.head

            # remove unused twist bones
            for idx in range(2, 33):
                tbone = get_edit_bone(bone_name + '_twist_' + str(idx) + side)
                if tbone:
                    delete_edit_bone(tbone)
        else:  # multiple twist bones case, position the bones steadily along the limb
            # add the original twist bone first
            if bone_name == "forearm":
                twist_bone.head = stretch_bone.head + (dist_vec * (twist_bones_amount))
                twist_bone.tail = end_bone.head
            elif bone_name == "arm":
                twist_bone.tail = twist_bone.head + dist_vec
                c_arm_twist_offset.tail = twist_bone.tail

            # remove other twist bones
            for idx in range(2, 33):
                tbone = get_edit_bone(bone_name + '_twist_' + str(idx) + side)
                if tbone:
                    delete_edit_bone(tbone)

            # add unused twist bones
            for twist_idx in range(2, twist_bones_amount + 1):
                new_tbone = bpy.context.active_object.data.edit_bones.new(bone_name + '_twist_' + str(twist_idx) + side)
                if bone_name == "forearm":
                    new_tbone.head = end_bone.head + (-dist_vec * (twist_idx))
                    new_tbone.tail = end_bone.head + (-dist_vec * (twist_idx - 1))
                    new_tbone.parent = stretch_bone
                elif bone_name == "arm":
                    new_tbone.head = twist_bone.head + (dist_vec * (twist_idx - 1))
                    new_tbone.tail = twist_bone.head + (dist_vec * (twist_idx))
                    new_tbone.parent = stretch_bone

                new_tbone.roll = twist_bone.roll

                # set layer
                new_tbone.layers[11] = True
                for idx, lay in enumerate(new_tbone.layers):
                    if idx != 11:
                        new_tbone.layers[idx] = False

            # set the stretch bone tail
            if not secondary_are_bendy_bones:
                if bone_name == "forearm":
                    stretch_bone.tail = get_edit_bone(bone_name + '_twist_' + str(twist_bones_amount) + side).head
                elif bone_name == "arm":
                    stretch_bone.head = get_edit_bone(bone_name + '_twist_' + str(twist_bones_amount) + side).tail
                    stretch_bone.parent = twist_bone

            # Bones Constraints
            bpy.ops.object.mode_set(mode='POSE')

            # remove old constraints on the stretch bone
            arm_stretch_pose = get_pose_bone('arm_stretch' + side)
            cns_cs = arm_stretch_pose.constraints.get('Copy Scale')
            cns_cl = arm_stretch_pose.constraints.get('Copy Location')
            if cns_cs:
                arm_stretch_pose.constraints.remove(cns_cs)
            if cns_cl:
                arm_stretch_pose.constraints.remove(cns_cl)

                # add constraints on the twist bones
            for twist_idx in range(2, twist_bones_amount + 1):
                tbone_pose = get_pose_bone(bone_name + '_twist_' + str(twist_idx) + side)

                # remove previous constraints if any
                for c in tbone_pose.constraints:
                    tbone_pose.constraints.remove(c)

                # add constraints
                if bone_name == 'forearm':
                    cns_copyrot = tbone_pose.constraints.new("COPY_ROTATION")
                    cns_copyrot.target = bpy.context.active_object
                    # cns_copyrot.subtarget = "hand_rot_twist"+side
                    cns_copyrot.subtarget = "forearm_twist" + side
                    cns_copyrot.influence = 1 - ((1 / twist_bones_amount) * (twist_idx - 1))
                    # cns_damptrack = tbone_pose.constraints.new("DAMPED_TRACK")
                    # cns_damptrack.target = bpy.context.active_object
                    # cns_damptrack.subtarget = 'hand'+side

                if bone_name == 'arm':
                    cns_cr = tbone_pose.constraints.new("COPY_ROTATION")
                    cns_cr.target = bpy.context.active_object
                    cns_cr.subtarget = "arm_twist" + side
                    cns_cr.influence = 1 - ((1 / twist_bones_amount) * (twist_idx - 1))

    set_limb_twist('forearm', 'hand')
    set_limb_twist('arm', 'forearm')

    # restore X mirror
    bpy.context.object.data.use_mirror_x = xmirror_state

    bpy.ops.object.mode_set(mode='EDIT')


def set_leg_twist(twist_bones_amount, side, bbones_ease_out=None):
    bpy.ops.object.mode_set(mode='EDIT')
    
    # disable X mirror
    xmirror_state = bpy.context.object.data.use_mirror_x
    bpy.context.object.data.use_mirror_x = False

    thigh_ref = get_edit_bone("thigh_ref" + side)
    leg_ref = get_edit_bone("leg_ref" + side)

    if thigh_ref == None or leg_ref == None:
        print("thigh_ref or leg_ref not found, can't set leg twists")
        return

    secondary_are_bendy_bones = False
    if bpy.context.object.arp_secondary_type == "BENDY_BONES":
        secondary_are_bendy_bones = True
        if bbones_ease_out != None:
            thigh_ref["arp_bbones_ease_out"] = bbones_ease_out

    # store the twist bones amount in a bone property to "save" it for later access
    thigh_ref["twist_bones_amount"] = twist_bones_amount


    def set_limb_twist(bone_name, end_bone_name):
        bpy.ops.object.mode_set(mode='EDIT')
        stretch_bone = get_edit_bone(bone_name + '_stretch' + side)
        twist_bone = get_edit_bone(bone_name + '_twist' + side)
        end_bone = get_edit_bone(end_bone_name + side)

        vec = None
        if bone_name == "leg":
            vec = end_bone.head - stretch_bone.head
        elif bone_name == "thigh":
            vec = end_bone.head - twist_bone.head

        dist_vec = vec / (twist_bones_amount + 1)

        if twist_bones_amount == 1:  # one twist bone case, position the bone halfway
            if bone_name == "leg":
                twist_bone.head = stretch_bone.head + (vec / 2)
                twist_bone.tail = end_bone.head
                if not secondary_are_bendy_bones:
                    stretch_bone.tail = twist_bone.head
            elif bone_name == "thigh":
                twist_bone.tail = end_bone.head - (vec / 2)
                if not secondary_are_bendy_bones:
                    stretch_bone.head = twist_bone.tail
                    stretch_bone.tail = end_bone.head

            # remove unused twist bones
            for idx in range(2, 33):
                tbone = get_edit_bone(bone_name + '_twist_' + str(idx) + side)
                if tbone:
                    delete_edit_bone(tbone)
        else:# multiple twist bones case, position the bones steadily along the limb
            # add the original twist bone first
            if bone_name == "leg":
                twist_bone.head = stretch_bone.head + (dist_vec * (twist_bones_amount))
                twist_bone.tail = end_bone.head
            elif bone_name == "thigh":
                twist_bone.tail = twist_bone.head + dist_vec

            # remove unused twist bones
            for idx in range(2, 33):
                tbone = get_edit_bone(bone_name + '_twist_' + str(idx) + side)
                if tbone:
                    delete_edit_bone(tbone)

            # add other twist bones
            for twist_idx in range(2, twist_bones_amount + 1):
                new_tbone = bpy.context.active_object.data.edit_bones.new(bone_name + '_twist_' + str(twist_idx) + side)
                if bone_name == "leg":
                    new_tbone.head = end_bone.head + (-dist_vec * (twist_idx))
                    new_tbone.tail = end_bone.head + (-dist_vec * (twist_idx - 1))
                    new_tbone.parent = stretch_bone
                elif bone_name == "thigh":
                    new_tbone.head = twist_bone.head + (dist_vec * (twist_idx - 1))
                    new_tbone.tail = twist_bone.head + (dist_vec * (twist_idx))
                    new_tbone.parent = stretch_bone

                new_tbone.roll = twist_bone.roll

                # set layer
                new_tbone.layers[11] = True
                for idx, lay in enumerate(new_tbone.layers):
                    if idx != 11:
                        new_tbone.layers[idx] = False

            # set the stretch bone tail
            if not secondary_are_bendy_bones:
                if bone_name == "leg":
                    stretch_bone.tail = get_edit_bone(bone_name + '_twist_' + str(twist_bones_amount) + side).head
                elif bone_name == "thigh":
                    stretch_bone.head = get_edit_bone(bone_name + '_twist_' + str(twist_bones_amount) + side).tail
                    stretch_bone.parent = twist_bone

            # Bones Constraints
            bpy.ops.object.mode_set(mode='POSE')

            # remove old location constraint on the stretch bone
            thigh_stretch_pose = get_pose_bone('thigh_stretch' + side)
            cns_cl = thigh_stretch_pose.constraints.get('Copy Location')
            if cns_cl:
                thigh_stretch_pose.constraints.remove(cns_cl)

                # add constraints on twist bones
            for twist_idx in range(2, twist_bones_amount + 1):
                tbone_pose = get_pose_bone(bone_name + '_twist_' + str(twist_idx) + side)
                # remove previous constraints if any
                for c in tbone_pose.constraints:
                    tbone_pose.constraints.remove(c)
                # add constraints
                if bone_name == 'leg':
                    cns_copyrot = tbone_pose.constraints.new("COPY_ROTATION")
                    cns_copyrot.target = bpy.context.active_object
                    cns_copyrot.subtarget = "leg_twist" + side
                    cns_copyrot.influence = 1 - ((1 / twist_bones_amount) * (twist_idx - 1))
                    # cns_damptrack = tbone_pose.constraints.new("DAMPED_TRACK")
                    # cns_damptrack.target = bpy.context.active_object
                    # cns_damptrack.subtarget = 'hand'+side
                if bone_name == 'thigh':
                    cns_cr = tbone_pose.constraints.new("COPY_ROTATION")
                    cns_cr.target = bpy.context.active_object
                    cns_cr.subtarget = "thigh_twist" + side
                    cns_cr.influence = 1 - ((1 / twist_bones_amount) * (twist_idx - 1))

    set_limb_twist('leg', 'foot')
    set_limb_twist('thigh', 'leg')

    # restore X mirror
    bpy.context.object.data.use_mirror_x = xmirror_state

    bpy.ops.object.mode_set(mode='EDIT')


def align_neck_master(_neck_master, _origin=None, _neck_vec=None, _neck_ref=None, _parent=None):
    _neck_master.head = _origin + (_neck_vec * 0.5)
    _neck_master.tail = _neck_master.head + (_neck_vec * 0.5)
    _neck_master.roll = _neck_ref.roll
    # parent
    _neck_master.parent = _parent


def set_neck(neck_count, twist=False, bendy_segments=1):
    context = bpy.context
    current_mode = context.mode
    active_bone = None

    # force twist to False if only one neck bone
    if neck_count == 1:
        twist = False

    if current_mode == 'POSE':
        try:
            active_bone = context.active_object.data.bones.active.name
        except:
            pass
    if current_mode == 'EDIT_ARMATURE':
        try:
            active_bone = context.active_object.data.edit_bones.active.name
        except:
            pass

    bpy.ops.object.mode_set(mode='EDIT')

    # update hack
    bpy.ops.object.editmode_toggle()
    bpy.ops.object.editmode_toggle()

    # disable the proxy picker to avoid bugs
    proxy_picker_state = disable_proxy_picker()

    # Active all layers
    layers_select = enable_all_armature_layers()

    side = ".x"
    # get the bone side
    if len(get_selected_edit_bones()):
        b_name = get_selected_edit_bones()[0].name
        print('bname', b_name)
        # only if it's a ref bone
        if len(b_name.split('_')) >= 2:
            if (b_name.split('_')[1][:3] == 'ref' and b_name.split('_')[0] == 'neck') or (
                    b_name.split('_')[0] == 'subneck' and b_name.split('_')[2][:3] == 'ref'):
                side = get_bone_side(b_name)
            else:
                print("No reference neck bone selected:", b_name)
    else:
        print("No bone selected")

    neck_bones_list = []
    neck_ref = get_edit_bone("neck_ref" + side)
    # store properties
    old_count = 0
    if "neck_count" in neck_ref.keys():
        old_count = neck_ref["neck_count"]
    neck_ref["neck_count"] = neck_count
    neck_ref["neck_twist"] = twist
    neck_ref["neck_bendy"] = bendy_segments

    c_neck = get_edit_bone("c_neck" + side)

    # More than 1 neck, add subneck
    if neck_count > 1:
        head_ref = get_edit_bone("head_ref"+side)
        head_bone = get_edit_bone("head"+side)
        neck_vec = None
        neck_origin = None
        neck_parent_ref = None
        c_neck_parent = None
        first_subneck = get_edit_bone("subneck_1_ref"+side)

        if first_subneck == None:
            neck_vec = neck_ref.tail - neck_ref.head
            neck_origin = neck_ref.head
            neck_parent_ref = neck_ref.parent
            c_neck_parent = c_neck.parent
        else:
            neck_vec = neck_ref.tail - first_subneck.head
            neck_origin = first_subneck.head.copy()
            neck_parent_ref = first_subneck.parent
            c_neck_parent = get_edit_bone("c_subneck_1"+side).parent

        # add target twist bone for the main neck bone
        neck_twist_tar_name = "neck_twist_tar"+side
        neck_twist_tar = get_edit_bone(neck_twist_tar_name)
        if twist:
            if neck_twist_tar == None:
                neck_twist_tar = bpy.context.active_object.data.edit_bones.new(neck_twist_tar_name)
                neck_twist_tar.parent = head_bone
            # coordinates
            copy_bone_transforms(neck_ref, neck_twist_tar)
            move_bone_to_bone(neck_twist_tar, head_ref)
            neck_twist_tar.tail = neck_twist_tar.head + (neck_twist_tar.tail-neck_twist_tar.head)*0.5

            neck_twist_tar.use_deform = False
            set_bone_layer(neck_twist_tar, 8)
        else:
            if neck_twist_tar:
                delete_edit_bone(neck_twist_tar)

        # Build the subneck bones chain
        for i in range(1, neck_count):
            # Refs
            subneck_ref_name = "subneck_"+str(i)+"_ref"+side
            subneck_ref = get_edit_bone(subneck_ref_name)
            if subneck_ref == None:
                subneck_ref = bpy.context.active_object.data.edit_bones.new(subneck_ref_name)
                # coordinates, only calculate if necessary
            if old_count != neck_count:
                subneck_ref.head = neck_origin + (neck_vec * (i - 1)) / neck_count
                subneck_ref.tail = subneck_ref.head + (neck_vec / neck_count)
                subneck_ref.roll = neck_ref.roll

            subneck_ref.use_deform = False
            set_bone_layer(subneck_ref, 17)
                # parent
            if i == 1:
                subneck_ref.parent = neck_parent_ref
            else:
                subneck_ref.parent = get_edit_bone("subneck_" + str(i - 1) + "_ref" + side)

            # Controllers
            c_subneck_name = "c_subneck_" + str(i) + side
            c_subneck = get_edit_bone(c_subneck_name)
            if c_subneck == None:
                c_subneck = bpy.context.active_object.data.edit_bones.new(c_subneck_name)
                # parent
            c_subneck.parent = get_edit_bone("c_subneck_" + str(i - 1) + side)

            if i != 1:# the first subneck is allowed to be disconnected from the spine or other parent, while others must be connected
                subneck_ref.use_connect = True

            # coordinates
            copy_bone_transforms(subneck_ref, c_subneck)

            c_subneck.use_deform = True
            set_bone_layer(c_subneck, 0)

            if twist:
                c_subneck.use_deform = False
                # Twist bones
                subneck_twist_name = "subneck_twist_" + str(i) + side
                subneck_twist = get_edit_bone(subneck_twist_name)
                if subneck_twist == None:
                    subneck_twist = bpy.context.active_object.data.edit_bones.new(subneck_twist_name)
                    # coordinates
                copy_bone_transforms(subneck_ref, subneck_twist)
                    # parent
                subneck_twist.parent = c_subneck

                set_bone_layer(subneck_twist, 8)

                # Twist bones targets
                subneck_twist_tar_name = "subneck_twist_tar_" + str(i) + side
                subneck_twist_tar = get_edit_bone(subneck_twist_tar_name)
                if subneck_twist_tar == None:
                    subneck_twist_tar = bpy.context.active_object.data.edit_bones.new(subneck_twist_tar_name)
                    # coordinates
                copy_bone_transforms(subneck_ref, subneck_twist_tar)
                subneck_twist_tar.tail = subneck_twist_tar.head + (subneck_twist_tar.tail-subneck_twist_tar.head)*0.5
                move_bone_to_bone(subneck_twist_tar, head_ref)
                    # parent
                subneck_twist_tar.parent = head_bone

                subneck_twist_tar.use_deform = False
                set_bone_layer(subneck_twist_tar, 8)

            # store in list
            neck_bones_list.append(subneck_ref.name)
            neck_bones_list.append(c_subneck.name)


        # Master neck controller
        # if does not exist, create it
        c_neck_master = get_edit_bone("c_neck_master" + side)
        if c_neck_master == None:
            c_neck_master = bpy.context.active_object.data.edit_bones.new("c_neck_master" + side)

        align_neck_master(_neck_master=c_neck_master, _origin=neck_origin, _neck_vec=neck_vec, _neck_ref=neck_ref, _parent=c_neck_parent)

        c_neck_master.use_deform = False
        set_bone_layer(c_neck_master, 0)
        neck_bones_list.append(c_neck_master.name)

        # Parent the neck_ref
        last_subneck_ref = get_edit_bone("subneck_" + str(neck_count - 1) + "_ref" + side)
        last_c_subneck = get_edit_bone("c_subneck_" + str(neck_count - 1) + side)
        neck_ref.parent = last_subneck_ref
        c_neck.parent = last_c_subneck
        c_neck.use_connect = True
        neck_ref.use_connect = True

    else:  # just one neck
        if get_edit_bone("subneck_1_ref" + side):
            neck_ref.head = get_edit_bone("subneck_1_ref" + side).head
            neck_ref.parent = get_edit_bone("subneck_1_ref" + side).parent
            c_neck.head, c_neck.tail = neck_ref.head, neck_ref.tail

            # delete the neck master controller
            if get_edit_bone("c_neck_master" + side):
                delete_edit_bone(get_edit_bone("c_neck_master" + side))

    # Delete unused subnecks
    for i in range(neck_count, 17):
        # ref
        subneck_ref = get_edit_bone('subneck_' + str(i) + '_ref' + side)
        if subneck_ref:
            delete_edit_bone(subneck_ref)
        # controllers
        subneck_cont = get_edit_bone('c_subneck_' + str(i) + side)
        if subneck_cont:
            delete_edit_bone(subneck_cont)
        # subneck twists
        subneck_twist = get_edit_bone('subneck_twist_' + str(i) + side)
        if subneck_twist:
            delete_edit_bone(subneck_twist)
        # subneck twist target
        subneck_twist_tar = get_edit_bone('subneck_twist_tar_' + str(i) + side)
        if subneck_twist_tar:
            delete_edit_bone(subneck_twist_tar)

    if not twist:
        # main neck twist target
        neck_twist_tar = get_edit_bone("neck_twist_tar" + side)
        if neck_twist_tar:
            delete_edit_bone(neck_twist_tar)

        for i in range(0, 17):
            # subneck twists
            subneck_twist = get_edit_bone('subneck_twist_' + str(i) + side)
            if subneck_twist:
                delete_edit_bone(subneck_twist)
            # subneck twist targets
            subneck_twist_tar = get_edit_bone('subneck_twist_tar_' + str(i) + side)
            if subneck_twist_tar:
                delete_edit_bone(subneck_twist_tar)

    # Set custom shapes band bone groups
    bpy.ops.object.mode_set(mode='POSE')

    neck_pbone = get_pose_bone("c_neck" + side)

    for bone_name in neck_bones_list:
        pbone = get_pose_bone(bone_name)

        # bone group
        if neck_pbone.bone_group:
            pbone.bone_group = neck_pbone.bone_group

        # custom shape
        if not "_ref" + side in bone_name:
            if bone_name.startswith("c_"):
                if neck_pbone.custom_shape and not "neck_master" in bone_name:
                    if pbone.custom_shape == None:
                        pbone.custom_shape = neck_pbone.custom_shape
                    get_data_bone(pbone.name).show_wire = True

                if "neck_master" in bone_name:
                    if pbone.custom_shape == None:
                        if bpy.data.objects.get("cs_square") == None:
                            append_from_arp(nodes=["cs_square"], type="object")
                        pbone.custom_shape = bpy.data.objects["cs_square"]
                    get_data_bone(pbone.name).show_wire = True



    for bone_name in neck_bones_list + ["c_neck" + side]:
        pbone = get_pose_bone(bone_name)

        if not ("_ref" + side) in bone_name and not "c_neck_master" in bone_name and not "twist" in bone_name:
            # Add neck master constraints
            if neck_count > 1:
                cns = None
                if pbone.constraints.get("neck_master_rot") == None:
                    cns = pbone.constraints.new('COPY_ROTATION')
                else:
                    cns = pbone.constraints["neck_master_rot"]

                cns.target = bpy.context.active_object
                cns.subtarget = "c_neck_master" + side
                cns.target_space = cns.owner_space = 'LOCAL'
                cns.use_offset = True
                cns.mix_mode = "BEFORE"
                cns.name = "neck_master_rot"
            else:
                # Remove neck master constraints
                cns = pbone.constraints.get("neck_master_rot")
                if cns:
                    pbone.constraints.remove(cns)

    # Add neck twist constraints
    main_neck = get_pose_bone("neck"+side)
    if twist:
        # custom property on c_neck to control automatic twist
        if not "neck_twist" in neck_pbone.keys():
            create_custom_prop(node=neck_pbone, prop_name="neck_twist", prop_val=1.0, prop_min=0.0, prop_max=1.0, prop_description="Influence of the selected neck controller automatic twist, following the head rotation")
        if not "neck_global_twist" in neck_pbone.keys():
            create_custom_prop(node=neck_pbone, prop_name="neck_global_twist", prop_val=1.0, prop_min=0.0, prop_max=1.0, prop_description="Global influence of automatic twist of multiple neck bones, following the head rotation")

        def set_twist_cns(cns, tar, inf, bone_name, _idx=None, _side=""):
            cns.target = bpy.context.active_object
            cns.subtarget = tar
            cns.target_space = cns.owner_space = "WORLD"
            cns.influence = inf
            cns.euler_order = "AUTO"
            cns.use_x = cns.use_y = cns.use_z = True
            # add influence driver
            dr_data_path = 'pose.bones["' + bone_name + '"].constraints["twist_rot"].influence'
            dr = bpy.context.active_object.animation_data.drivers.find(dr_data_path)
            if dr == None:
                dr = bpy.context.active_object.driver_add(dr_data_path, -1)
            dr.driver.expression = 'global * local * ' + str(inf)
                # vars
                # 1.global twist
            if len(dr.driver.variables) == 0:
                var1 = dr.driver.variables.new()
            else:
                var1 = dr.driver.variables[0]
            var1.type = 'SINGLE_PROP'
            var1.name = 'global'
            var1.targets[0].id = bpy.context.active_object
            var1.targets[0].data_path = 'pose.bones["' + neck_pbone.name + '"].["neck_global_twist"]'
                # 2.local twist
            if len(dr.driver.variables) == 1:
                var2 = dr.driver.variables.new()
            else:
                var2 = dr.driver.variables[1]
            var2.type = 'SINGLE_PROP'
            var2.name = 'local'
            var2.targets[0].id = bpy.context.active_object
            if _idx:
                var2.targets[0].data_path = 'pose.bones["' + 'c_subneck_'+str(_idx)+_side+'"].["neck_twist"]'
            else:
                var2.targets[0].data_path = 'pose.bones["' +neck_pbone.name+'"].["neck_twist"]'


        def set_damped_track_cns(cns, tar):
            cns.target = bpy.context.active_object
            cns.subtarget = tar

        # main neck twist
        if main_neck:
            cns = main_neck.constraints.get("twist_rot")
            if cns == None:
                cns = main_neck.constraints.new("COPY_ROTATION")
                cns.name = "twist_rot"
            set_twist_cns(cns, "neck_twist_tar"+side, 1.0, main_neck.name)

            cns_d = main_neck.constraints.get("damped_track")
            if cns_d == None:
                cns_d = main_neck.constraints.new("DAMPED_TRACK")
                cns_d.name = "damped_track"
            set_damped_track_cns(cns_d, "head.x")

        # subnecks twist
        for idx in range(1, neck_count+1):
            c_subneck = get_pose_bone("c_subneck_"+str(idx)+side)
            twist_bone_name = "subneck_twist_"+str(idx)+side
            next_twist_bone_name = "subneck_twist_"+str(idx+1)+side
            if idx == neck_count-1:
                next_twist_bone_name = "neck.x"
            pbone = get_pose_bone(twist_bone_name)
            if pbone == None:
                continue

            cns = pbone.constraints.get("twist_rot")
            if cns == None:
                cns = pbone.constraints.new("COPY_ROTATION")
            cns.name = "twist_rot"
            # twist property to control automatic twist influence
            if not "neck_twist" in c_subneck.keys():
                create_custom_prop(node=c_subneck, prop_name="neck_twist", prop_val=1.0, prop_min=0.0, prop_max=1.0, prop_description="Influence of the selected neck controller automatic twist, following the head rotation")

            set_twist_cns(cns, "subneck_twist_tar_"+str(idx)+side, (1/neck_count)*idx, pbone.name, _idx=idx, _side=side)

            cns_d = pbone.constraints.get("damped_track")
            if cns_d == None:
                cns_d = pbone.constraints.new("DAMPED_TRACK")
            cns_d.name = "damped_track"
            set_damped_track_cns(cns_d, next_twist_bone_name)

    else:# no twist
        # delete twist custom properties
        if "neck_twist" in neck_pbone.keys():
            del neck_pbone["neck_twist"]
        if "neck_global_twist" in neck_pbone.keys():
            del neck_pbone["neck_global_twist"]

        for idx in range(1, neck_count):
            c_subneck = get_pose_bone("c_subneck_"+str(idx)+side)
            if "neck_twist" in c_subneck.keys():
                del c_subneck["neck_twist"]

        if main_neck:
            cns = main_neck.constraints.get("twist_rot")
            if cns:
                main_neck.constraints.remove(cns)
            cns_d = main_neck.constraints.get("damped_track")
            if cns_d:
                main_neck.constraints.remove(cns_d)

        # neck master custom shape
    if neck_count > 1:
        neck_pbone.custom_shape_transform = None
        if get_pose_bone("c_neck_master" + side):
            get_pose_bone("c_neck_master" + side).custom_shape_transform = get_pose_bone("c_neck" + side)
            set_custom_shape_scale(get_pose_bone("c_neck_master" + side), 0.4)
        if get_pose_bone("c_p_neck" + side):
            neck_pbone.custom_shape_transform = get_pose_bone('c_p_neck' + side)

    # Set bendy bones
    main_neck.bone.bbone_segments = bendy_segments

    # restore saved mode
    restore_current_mode(current_mode)

    # Restore selected bone
    if active_bone:
        if current_mode == 'POSE':
            if get_pose_bone(active_bone):
                bpy.context.active_object.data.bones.active = get_pose_bone(active_bone).bone

        if current_mode == 'EDIT':
            if get_edit_bone(active_bone):
                bpy.context.active_object.data.edit_bones.active = get_edit_bone(active_bone)

    # Restore layers
    restore_armature_layers(layers_select)

    # restore picker
    restore_proxy_picker(proxy_picker_state)

    # end set_neck(neck_count, twist)
    
    
def align_spine_master_bones(rig):
    # constrained bones
    for idx in range(1, rig.rig_spine_count):
        str_idx = '%02d' % idx            
        c_spine_name = 'c_spine_'+str_idx+'.x'
        c_spine = get_edit_bone(c_spine_name)
        spine_cns_name = 'spine_'+str_idx+'_cns.x'                   
        spine_cns = get_edit_bone(spine_cns_name) 
        if spine_cns:
            copy_bone_transforms(c_spine, spine_cns)
            spine_cns.tail = spine_cns.head + (spine_cns.tail-spine_cns.head)*0.7
        
        # spine master bone
        if idx == rig.rig_spine_count-1:                
            c_spine_master_name = 'c_spine_master.x'
            c_spine_master = get_edit_bone(c_spine_master_name)     
            if c_spine_master:
                copy_bone_transforms(c_spine, c_spine_master)
            
    # spine stretchy
    spine_stretchy_name = 'spine_stretchy.x'
    spine_stretchy = get_edit_bone(spine_stretchy_name)
    if spine_stretchy:
        spine_stretchy.head = get_edit_bone('root_ref.x').head.copy()
        last_str_idx = '%02d' % (rig.rig_spine_count-1)
        spine_tip_bone = get_edit_bone('spine_'+last_str_idx+'.x')
        spine_stretchy.tail = spine_tip_bone.head.copy()
        align_bone_x_axis(spine_stretchy, spine_tip_bone.x_axis)



def set_spine(bottom=False, align_root_master=True, grid_align=False, spine_master_enabled=False, spine_master_space='LOCAL', spine_master_stretchy=False):
    context = bpy.context
    current_mode = context.mode
    
    if get_data_bone("root_ref.x") == None:
        print("root_ref.x not found, cannot set spine bones")
        return

    # disable the proxy picker to avoid bugs
    proxy_picker_state = disable_proxy_picker()

    active_bone_name = None
    rig = bpy.data.objects.get(bpy.context.active_object.name)
    rig_name = rig.name
    rig_add = get_rig_add(rig)

    bpy.ops.object.mode_set(mode='EDIT')
    
    try:
        active_bone_name = rig.data.edit_bones.active.name
    except:# no active bone
        pass
    
    # store params
    root_bone = get_edit_bone("root_ref.x")
    root_bone["align_root_master"] = align_root_master
    root_bone['spine_master'] = spine_master_enabled
    root_bone['spine_master_space'] = spine_master_space
    root_bone['spine_master_stretchy'] = spine_master_stretchy
    bpy.ops.armature.select_all(action='DESELECT')

    # Active all layers
    layers_select = enable_all_armature_layers()

    all_is_there = False

    if rig.rig_spine_count >= 1:
        # get current root-tip positions of the spine
        spine_root_tip = [get_edit_bone("root_ref.x").head.copy(), get_edit_bone("root_ref.x").tail.copy()]
        # get the root and tip children to restore afterward
        tip_children = []
        total_spine_found = 1
        for idx in range(1, 33):
            prev_str_idx = '%02d' % (idx-1)
            str_idx = '%02d' % idx
            spine_ref = get_edit_bone('spine_'+ str_idx+'_ref.x')
            if spine_ref:
                spine_root_tip[1] = spine_ref.tail.copy()
                total_spine_found += 1
            else:
                # store the tip children
                spine_bone_name = 'spine_'+prev_str_idx+'_ref.x'
                if idx == 1:
                    spine_bone_name = 'root_ref.x'

                spine_ref_previous = get_edit_bone(spine_bone_name)
                if spine_ref_previous:
                    for b in rig.data.edit_bones:
                        if b.parent and b.layers[17]:
                            if b.parent == spine_ref_previous and not "spine_" in b.name:
                                tip_children.append(b.name)
                break
        
        
        # if all spine bones are already there, return
        if total_spine_found == rig.rig_spine_count:
            all_is_there = True

        if not all_is_there or grid_align:
            spine_vec = spine_root_tip[1] - spine_root_tip[0]

            # delete out of range spine bones if any
            for idx in range(rig.rig_spine_count, 33):
                str_idx = '%02d' % idx
                # ref bones
                spine_ref = get_edit_bone('spine_'+str_idx +'_ref.x')
                if spine_ref:
                    delete_edit_bone(spine_ref)

                # control bones
                spine_cont = get_edit_bone("c_spine_"+str_idx+".x")
                if spine_cont:
                    delete_edit_bone(spine_cont)

                # control bend bones
                spine_bend_cont = get_edit_bone("c_spine_"+str_idx+"_bend.x")
                if spine_bend_cont:
                    delete_edit_bone(spine_bend_cont)

                # deform bones
                spine_def = get_edit_bone('spine_'+str_idx+'.x')
                if spine_def:
                    delete_edit_bone(spine_def)

                # delete the waist bend bone if only 1 spine bone
                if rig.rig_spine_count == 1:
                    waist_bend = get_edit_bone("c_waist_bend.x")
                    if waist_bend:
                        delete_edit_bone(waist_bend)

                        # from the rig_add too
                        if rig_add:
                            edit_rig(rig_add)
                            waist_bend_add = get_edit_bone("c_waist_bend.x")
                            if waist_bend_add:
                                delete_edit_bone(waist_bend_add)

                            bpy.ops.object.mode_set(mode='OBJECT')
                            rig_add.select_set(state=False)
                            edit_rig(bpy.data.objects[rig_name])

                # display position bones
                spine_c_p = get_edit_bone("c_p_spine_"+str_idx+".x")
                if spine_c_p:
                    delete_edit_bone(spine_c_p)
                    
                # constrained bones
                spine_cns = get_edit_bone('spine_'+str_idx+'_cns.x')
                if spine_cns:
                    delete_edit_bone(spine_cns)

            # Create new bones
            bones_created = []
            spine_bones_ref = ['root_ref.x']
            for idx in range(1, rig.rig_spine_count):
                str_idx = '%02d' % idx
                ref_name = 'spine_'+str_idx+'_ref.x'
                spine_bones_ref.append(ref_name)

            for idx, ref_name in enumerate(spine_bones_ref):
                str_idx = '%02d' % idx
                # 1.ref bones
                spine_ref = get_edit_bone(ref_name)
                if not spine_ref:
                    spine_ref = rig.data.edit_bones.new(ref_name)
                    spine_ref.use_deform = False
                    bones_created.append(spine_ref.name)
                    # layer
                    set_bone_layer(spine_ref, 17)

                # reconnect spine tip children
                if idx == len(spine_bones_ref) - 1:
                    for bname in tip_children:
                        child = get_edit_bone(bname)
                        if child:
                            child.parent = spine_ref

                # set transforms
                spine_ref.head = spine_root_tip[0] + (spine_vec * (idx)) / (rig.rig_spine_count)
                spine_ref.tail = spine_ref.head + (spine_vec / (rig.rig_spine_count))

                # parent
                if idx > 0:# no parent for the root bone
                    spine_ref.parent = get_edit_bone(spine_bones_ref[idx - 1])
                    spine_ref.use_connect = True

                # 2.control bones
                cont_name = 'c_' + ref_name.replace('_ref', '')
                spine_cont = get_edit_bone(cont_name)
                if not spine_cont:
                    spine_cont = bpy.context.active_object.data.edit_bones.new(cont_name)
                    spine_cont.use_deform = False
                    bones_created.append(spine_cont.name)
                    # set layer
                    set_bone_layer(spine_cont, 0)
                    spine_cont.layers[0] = True

                # set transforms
                if idx > 0:# no new transforms for the root bone
                    spine_cont.head, spine_cont.tail, spine_cont.roll = spine_ref.head.copy(), spine_ref.tail.copy(), spine_ref.roll
                    # parent
                    if idx != 1:
                        previous_cont_name = 'c_' + spine_bones_ref[idx - 1].replace('_ref', '')
                    else:
                        previous_cont_name = 'c_' + spine_bones_ref[idx - 1].replace('_ref', '_master')
                    spine_cont.parent = get_edit_bone(previous_cont_name)

                # 3.deforming bones
                spine_def_name = cont_name[2:]
                spine_def = get_edit_bone(spine_def_name)
                if not spine_def:
                    spine_def = bpy.context.active_object.data.edit_bones.new(spine_def_name)
                    bones_created.append(spine_def_name)
                    # set layer
                    set_bone_layer(spine_def, 8)

                # set transforms
                if idx > 0:  # no new transforms for the root bone
                    spine_def.head, spine_def.tail, spine_def.roll = spine_ref.head.copy(), spine_ref.tail.copy(), spine_ref.roll
                    # parent
                    previous_cont_name = 'c_' + spine_bones_ref[idx].replace('_ref', '')
                    spine_def.parent = get_edit_bone(previous_cont_name)

                # 4.control bend bones
                spine_bend_cont_name = cont_name.replace(str_idx, str_idx + '_bend')
                spine_bend_cont = get_edit_bone(spine_bend_cont_name)
                if not spine_bend_cont:
                    spine_bend_cont = bpy.context.active_object.data.edit_bones.new(spine_bend_cont_name)
                    bones_created.append(spine_bend_cont.name)
                    # set layer
                    set_bone_layer(spine_bend_cont, 1)

                if idx > 0:
                    spine_bend_cont.head = ((spine_cont.tail + spine_cont.head) * 0.5)
                    spine_bend_cont.tail = spine_cont.head
                    # parent
                    previous_cont_name = 'c_' + spine_bones_ref[idx].replace('_ref', '')
                    spine_bend_cont.parent = get_edit_bone(previous_cont_name)

                # add the waist bend bone if more than 1 spine bone
                if rig.rig_spine_count > 1:
                    waist_bend = get_edit_bone("c_waist_bend.x")
                    if not waist_bend:
                        waist_bend = bpy.context.active_object.data.edit_bones.new("c_waist_bend.x")
                        waist_bend.use_deform = False
                        bones_created.append(waist_bend.name)

                        # set transforms
                        root_ref = get_edit_bone('root_ref.x')
                        waist_bend.head = root_ref.tail
                        waist_bend.tail = root_ref.tail + (root_ref.tail - root_ref.head) * 0.5
                        waist_transforms = [waist_bend.head.copy(), waist_bend.tail.copy(), waist_bend.roll]
                        # set parent
                        waist_bend.parent = get_edit_bone("c_root.x")
                        # set layer
                        set_bone_layer(waist_bend, 1)

                        # create the waist_bend bone on the rig_add too
                        if rig_add:
                            edit_rig(rig_add)
                            waist_bend_add_name = "c_waist_bend.x"
                            waist_bend_add = get_edit_bone(waist_bend_add_name)
                            if not waist_bend_add:
                                waist_bend_add = bpy.context.active_object.data.edit_bones.new(waist_bend_add_name)
                                waist_bend_add.head, waist_bend_add.tail, waist_bend_add.roll = waist_transforms[0], \
                                                                                                waist_transforms[1], \
                                                                                                waist_transforms[2]

                                # set constraint
                                bpy.ops.object.mode_set(mode='POSE')
                                pb_waist_bend_add = get_pose_bone(waist_bend_add_name)
                                cns = pb_waist_bend_add.constraints.new("COPY_TRANSFORMS")
                                cns.target = bpy.data.objects[rig_name]
                                cns.subtarget = waist_bend_add_name
                                cns.owner_space = cns.target_space = "LOCAL"

                            bpy.ops.object.mode_set(mode='OBJECT')
                            bpy.ops.object.select_all(action='DESELECT')
                            edit_rig(bpy.data.objects[rig_name])

            # Pose mode only
            bpy.ops.object.mode_set(mode='POSE')
            
            
            for bname in bones_created:
                # set custom shapes
                if '_ref' in bname or not bname.startswith("c_"):
                    continue

                # main controls
                if not '_bend' in bname:
                    cs_name = 'cs_spine'
                    cs = bpy.data.objects.get(cs_name)
                    if not cs:
                        append_from_arp(nodes=[cs_name], type='object')
                        cs = bpy.data.objects.get(cs_name)
                    
                    get_pose_bone(bname).custom_shape = cs
                    set_custom_shape_scale(get_pose_bone(bname), 0.45)
                    
                else:
                    # bend controls
                    cs_name = 'cs_torus_01'
                    cs = bpy.data.objects.get(cs_name)
                    if not cs:
                        append_from_arp(nodes=[cs_name], type='object')
                        cs = bpy.data.objects.get(cs_name)
                    
                    get_pose_bone(bname).custom_shape = cs
                    set_custom_shape_scale(get_pose_bone(bname), 1.2)

                # set bone group
                get_pose_bone(bname).bone_group = bpy.context.active_object.pose.bone_groups.get('body.x')

                # set rotation mode
                get_pose_bone(bname).rotation_mode = 'XYZ'

                bpy.ops.object.mode_set(mode='EDIT')

    bpy.ops.object.mode_set(mode='EDIT')
    
    # add spine master
    c_spine_master_name = 'c_spine_master.x'
    spine_stretchy_name = 'spine_stretchy.x'
    c_root_master_name = ard.spine_bones_dict['c_root_master']
    
    if rig.rig_spine_count >= 2 and spine_master_enabled:        
        
        for idx in range(1, rig.rig_spine_count):
            str_idx = '%02d' % idx
            prev_str_idx = '%02d' % (idx-1)
            
            # add constrained bones 
            c_spine_name = 'c_spine_'+str_idx+'.x'
            c_spine = get_edit_bone(c_spine_name)
            c_prev_spine_name = 'c_spine_'+prev_str_idx+'.x'            
            if idx == 1:
                c_prev_spine_name = c_root_master_name
            c_prev_spine = get_edit_bone(c_prev_spine_name)
            
            spine_cns_name = 'spine_'+str_idx+'_cns.x'                   
            spine_cns = create_edit_bone(spine_cns_name)            
            spine_cns.head, spine_cns.tail = [0,0,0], [0,0,1]# aligned later            
            set_bone_layer(spine_cns, 8)  
            spine_cns.parent = c_prev_spine
            c_spine.parent = spine_cns
            
            # add spine master
            if idx == rig.rig_spine_count-1:
                c_spine_master = create_edit_bone(c_spine_master_name)
                c_spine_master.head, c_spine_master.tail = [0,0,0], [0,0,1]# aligned later                
                set_bone_layer(c_spine_master, 0)
                c_spine_master.parent = get_edit_bone(c_root_master_name)                
        
        
        if spine_master_stretchy:            
            spine_stretchy_eb = create_edit_bone(spine_stretchy_name)
            spine_stretchy_eb.head, spine_stretchy_eb.tail = [0,0,0], [0,0,1]# aligned later            
            set_bone_layer(spine_stretchy_eb, 8)
            spine_stretchy_eb.parent = get_edit_bone(c_root_master_name)
        else:# remove master stretchy
            spine_stretchy_eb = get_edit_bone(spine_stretchy_name)
            if spine_stretchy_eb:
                delete_edit_bone(spine_stretchy_eb)
                
        align_spine_master_bones(rig)
        
        bpy.ops.object.mode_set(mode='POSE')
        
        # set master shape
        spine_master_pb = get_pose_bone(c_spine_master_name)
        if spine_master_pb.custom_shape == None:
            set_bone_custom_shape(spine_master_pb, 'cs_spine_master')
            
        spine_master_pb.rotation_mode = 'XYZ'
        
        if spine_master_pb.bone_group == None:
            set_bone_color_group(rig, spine_master_pb, 'body.x')
            
        str_idx = '%02d' % (rig.rig_spine_count-1)
        tip_spine_name = 'c_spine_'+str_idx+'.x'
        spine_master_pb.custom_shape_transform = get_pose_bone(tip_spine_name)
        c_root_master_pb = get_pose_bone(c_root_master_name)
        
        # set constraints
        #   main spine constraints
        for idx in range(1, rig.rig_spine_count):
            str_idx = '%02d' % idx
            spine_cns_bone_name = 'spine_'+str_idx+'_cns.x'
            spine_cns_pb = get_pose_bone(spine_cns_bone_name)
            cns_name = 'Copy Transforms'
            cns_copy = spine_cns_pb.constraints.get(cns_name)
            if cns_copy == None:
                cns_copy = spine_cns_pb.constraints.new('COPY_TRANSFORMS')
                cns_copy.name = cns_name
                
            cns_copy.target = rig
            cns_copy.subtarget = c_spine_master_name
            cns_copy.mix_mode = 'BEFORE'
            cns_copy.target_space = 'LOCAL'
            
            if bpy.app.version >= (2, 92, 0) and spine_master_space == 'CUSTOM':# custom space
                cns_copy.owner_space = 'CUSTOM'
                cns_copy.space_object = rig
                cns_copy.space_subtarget = c_spine_master_name
            else:
                cns_copy.owner_space = 'LOCAL'
                
            cns_copy.influence = 1/(rig.rig_spine_count-1)
            
            #   stretchy
            spine_name = 'spine_'+str_idx+'.x'
            spine_cns_pb = get_pose_bone(spine_name)
            
            if spine_master_stretchy:                
                # CopyScale
                cns_name = 'Copy Scale'
                cns_scale = spine_cns_pb.constraints.get(cns_name)
                if cns_scale == None:
                    cns_scale = spine_cns_pb.constraints.new('COPY_SCALE')
                    cns_scale.name = cns_name
                cns_scale.target = rig
                cns_scale.subtarget = spine_stretchy_name
                cns_scale.use_y = False
                cns_scale.target_space = cns_scale.owner_space = 'LOCAL'                
                
            else:
                cns_scale = spine_cns_pb.constraints.get('Copy Scale')
                if cns_scale:
                    spine_cns_pb.constraints.remove(cns_scale)
               
       
        prop_spine_stretch_name = 'spine_stretch_volume'
        
        if spine_master_stretchy:
            # StretchTo
            spine_stretchy_pb = get_pose_bone(spine_stretchy_name)
            cns_name = 'Stretch To'
            cns_stretch = spine_stretchy_pb.constraints.get(cns_name)
            if cns_stretch == None:
                cns_stretch = spine_stretchy_pb.constraints.new('STRETCH_TO')
                cns_stretch.name = cns_name
            cns_stretch.target = rig
            cns_stretch.subtarget = c_spine_master_name
            
            # add stretch amount prop
            create_custom_prop(node=c_root_master_pb, prop_name=prop_spine_stretch_name, prop_val=1.0, prop_min=0.0, prop_max=10.0, prop_description="", default=1.0)            
            
            # add stretch volume driver
            add_driver_to_prop(rig, 'pose.bones["'+spine_stretchy_name+'"].constraints["'+cns_name+'"].bulge', 'pose.bones["'+c_root_master_name+'"]["'+prop_spine_stretch_name+'"]', array_idx=-1, exp="var")
        else:
            # remove prop
            if prop_spine_stretch_name in c_root_master_pb.keys():
                del c_root_master_pb[prop_spine_stretch_name]

        
        bpy.ops.object.mode_set(mode='EDIT')
        
    else:# spine master disabled
        # remove constrained bones
        for idx in range(1, 33):
            str_idx = '%02d' % idx
            spine_cns_name = 'spine_'+str_idx+'_cns.x'                   
            spine_cns = create_edit_bone(spine_cns_name)            
            if spine_cns:
                delete_edit_bone(spine_cns)
            
        # remove spine_master
        c_spine_master = get_edit_bone(c_spine_master_name)
        if c_spine_master:
            delete_edit_bone(c_spine_master)
            
        # remove spine master stretchy
        spine_stretchy = get_edit_bone(spine_stretchy_name)
        if spine_stretchy:
            delete_edit_bone(spine_stretchy)
    
    
    # set picker 
    _set_picker_spine()

    # Set bottom bones
    if bottom:
        bpy.ops.object.mode_set(mode='EDIT')
        
        root_ref = get_edit_bone("root_ref.x")
        bones_coords = {}

        if root_ref:
            # create bottoms
            for side in (".l", ".r"):
                # ref bones
                bot_ref = get_edit_bone("bot_bend_ref" + side)
                if not bot_ref:
                    bot_ref = bpy.context.active_object.data.edit_bones.new("bot_bend_ref" + side)
                    # Set layers
                    set_bone_layer(bot_ref, 17)

                    root_ref = get_edit_bone("root_ref.x")

                    # Set transforms
                    fac = 1
                    if side == ".r":
                        fac = -1
                    bot_ref.head = root_ref.head + (
                            -root_ref.z_axis.normalized() * (root_ref.tail - root_ref.head).magnitude) + (
                                           root_ref.x_axis.normalized() * (
                                           root_ref.tail - root_ref.head).magnitude * 0.5 * fac)
                    bot_ref.tail = bot_ref.head + (
                            -root_ref.z_axis.normalized() * (root_ref.tail - root_ref.head).magnitude * 0.3)
                    bot_ref.roll = 0

                    bones_coords[bot_ref.name] = bot_ref.head.copy(), bot_ref.tail.copy(), bot_ref.roll

                    # Set deform
                    bot_ref.use_deform = False

                # control bones
                bot_control = get_edit_bone("c_bot_bend" + side)
                if not bot_control:
                    bot_control = bpy.context.active_object.data.edit_bones.new("c_bot_bend" + side)

                    # Set layers
                    set_bone_layer(bot_control, 1)

                    # Set transforms
                    bot_control.head, bot_control.tail, bot_control.roll = bot_ref.head.copy(), bot_ref.tail.copy(), bot_ref.roll

                    # Set deform
                    # bot_control.use_deform = False

                    # Parent
                    root_bend = get_edit_bone("c_root_bend.x")
                    c_root = get_edit_bone("c_root.x")
                    if root_bend:
                        bot_control.parent = root_bend
                    elif c_root:
                        bot_control.parent = c_root

                    # custom shape
                bpy.ops.object.mode_set(mode='POSE')
                pb = get_pose_bone("c_bot_bend" + side)
                cs = bpy.data.objects.get("cs_torus_01")
                if cs and pb.custom_shape == None:
                    pb.custom_shape = cs

                # bone groups
                group_name = "body" + side
                grp = bpy.context.active_object.pose.bone_groups.get(group_name)
                if grp:
                    pb.bone_group = grp

                bpy.ops.object.mode_set(mode='EDIT')
        else:
            print("Root bone not found, could not set the bottom bones")

    else:# no bottom
        bpy.ops.object.mode_set(mode='EDIT')
        
        root_ref = get_edit_bone("root_ref.x")

        if root_ref:
            for side in (".l", ".r"):
                
                # delete bottoms
                bottom_bones_ref = ["bot_bend_ref"]
                bottom_bones_control = ["c_bot_bend"]
                for b in bottom_bones_ref + bottom_bones_control:
                    ebone = get_edit_bone(b + side)
                    if ebone:
                        delete_edit_bone(ebone)

                
        else:
            print("Root bone not found, could not set the bottom bones")

        # Rig_add bones
        if rig_add:
            edit_rig(rig_add)
            for side in (".l", ".r"):
                n = "c_bot_bend" + side
                bot_bone = get_edit_bone(n)
                if bot_bone:
                    delete_edit_bone(bot_bone)

            bpy.ops.object.mode_set(mode='OBJECT')
            bpy.ops.object.select_all(action='DESELECT')
            hide_object(rig_add)
            set_active_object(rig_name)

    # restore layers    
    restore_armature_layers(layers_select)
    
    # restore saved mode
    restore_current_mode(current_mode)
    
    if active_bone_name and not 'spine_03' in active_bone_name:
        if current_mode == 'POSE':
            bpy.context.active_object.data.bones.active = get_pose_bone(active_bone_name).bone

        if current_mode == 'EDIT_ARMATURE':
            rig.data.edit_bones.active = get_edit_bone(active_bone_name)
            
    # restore picker
    restore_proxy_picker(proxy_picker_state)

    # end set_spine()


def set_eyetargets_distance(eye_target_dist):
    context = bpy.context

    # get the bone side
    side = ""

    if len(get_selected_edit_bones()):
        b_name = get_selected_edit_bones()[0].name
        side = ".x"
        if '_dupli_' in b_name:
            side = b_name[-12:]
            side = side[:-2] + ".x"
    else:
        print("No bone selected")

        # set eye target distance
    head_ref = get_edit_bone("head_ref" + side)
    head_ref["eye_target_dist"] = eye_target_dist

    # end set_eyetargets_distance()


def set_jaw_rotation_location(rot_state, auto_lips_visual, loc_state):
    context = bpy.context
    rig_name = bpy.context.active_object.name
    xmirror_state = bpy.context.active_object.data.use_mirror_x
    bpy.context.object.data.use_mirror_x = False

    # get the bone side
    side = ""

    if len(get_selected_edit_bones()) > 0:
        b_name = get_selected_edit_bones()[0].name
        side = ".x"
        side_id = ""
        if '_dupli_' in b_name:
            side_id = b_name[-12:][:-2]
            side = b_name[-12:]
            side = side[:-2] + ".x"
    else:
        print("No bone selected")

    head_ref = get_edit_bone("head_ref" + side)
    head_ref["arp_jaw_rotation"] = rot_state
    head_ref["arp_jaw_location"] = loc_state
    jaw_speed = 1.0
    if 'jaw_speed' in head_ref.keys():
        jaw_speed = head_ref['jaw_speed']    

    bpy.ops.object.mode_set(mode='POSE')    
   
    jaw_pbone = get_pose_bone("jawbone" + side)
    
    if jaw_pbone:
        cns = jaw_pbone.constraints.get("Damped Track")
        if cns:
            if rot_state:
                cns.influence = 0.0
            else:
                cns.influence = 1.0 if jaw_speed >= 1.0 else jaw_speed

        if auto_lips_visual:  # we need a new transform constraint to make the c_lips_01 follow the jaw rotation
            for _side in [".l", ".r"]:
                bname = "lips_top_01_follow" + side_id + _side
                follow_pbone = get_pose_bone(bname)
                cns2 = follow_pbone.constraints.get("MoveWithJawRot")
                if rot_state:
                    if cns2 == None:
                        cns2 = follow_pbone.constraints.new("TRANSFORM")
                        cns2.name = "MoveWithJawRot"
                        cns2.target = bpy.context.active_object
                        cns2.subtarget = "c_jawbone" + side
                        cns2.use_motion_extrapolate = True
                        cns2.map_from = "ROTATION"
                        cns2.map_to = "LOCATION"
                        cns2.from_max_x_rot = 0.174
                        cns2.map_to_y_from = "X"
                        cns2.map_to_x_from = "Y"
                        cns2.to_max_y = -0.01
                        cns2.target_space = cns2.owner_space = "LOCAL"
                        cns2.influence = 0.2
                else:
                    if cns2:
                        cns2.influence = 0.0

        if loc_state:
            cns_loc = jaw_pbone.constraints.get("jawbone" + side)
            if cns_loc == None:
                cns_loc = jaw_pbone.constraints.new("COPY_LOCATION")
                cns_loc.name = "Copy Location"
                cns_loc.target = bpy.context.active_object
                cns_loc.subtarget = "c_jawbone" + side
                cns_loc.target_space = cns_loc.owner_space = "LOCAL"
        else:
            cns_loc = jaw_pbone.constraints.get("jawbone" + side)
            if cns_loc:
                jaw_pbone.constraints.remove(cns_loc)

    bpy.ops.object.mode_set(mode='EDIT')

    bpy.context.active_object.data.use_mirror_x = xmirror_state
    # end set_jaw_rotation_location()
    

def set_lips_roll_constraints(rig, head_side, enable=False, global_speed=1.0, speed_dict={}):
    cont_lips = ard.get_variable_lips(head_side+'.x', type='CONTROLLER')
    additional_cont_lips = ['c_lips_top'+head_side+'.x', 'c_lips_bot'+head_side+'.x',
                'c_lips_bot'+head_side+'.l', 'c_lips_bot'+head_side+'.r',
                'c_lips_top'+head_side+'.l', 'c_lips_top'+head_side+'.r']
    
    if enable:            
        print("  Setup lips roll constraints...")
        lips_amount = (len(speed_dict)-8)/4
        for c_name in cont_lips + additional_cont_lips:
            idx = ard.get_lip_idx(c_name)            
            pb = get_pose_bone(c_name)
            if pb == None:
                continue
            cns_name = 'Transf_Roll'
            cns = pb.constraints.get(cns_name)
            if cns == None:
                cns = pb.constraints.new('TRANSFORM')
                cns.name = cns_name
                cns.use_motion_extrapolate = True                    
            cns.target = rig
            cns.subtarget = 'c_lips_roll_top'+head_side+'.x' if 'top' in c_name else 'c_lips_roll_bot'+head_side+'.x'
            cns.owner_space = cns.target_space = 'LOCAL'
            cns.map_from = 'LOCATION'
            cns.from_min_z = 0.0
            cns.from_max_z = 1.0
            cns.map_to = 'ROTATION'
            cns.to_min_z_rot = 0.0
            side_fac = -1 if c_name.endswith('.r') else 1
            if 'bot' in c_name:
                side_fac *= -1
            soft_fac = 1# the closer to the center, the higher
            if not c_name.endswith('.x'):
                soft_fac = 1 - ((idx+1)/(lips_amount+1))
            cns.to_max_z_rot = math.radians(1000.0 * global_speed * speed_dict[c_name] * soft_fac) * side_fac
        print('  roll set.')
    else:            
        print("  Remove lips roll constraints...")
        for c_name in cont_lips + additional_cont_lips:
            pb = get_pose_bone(c_name)
            if c_name == None:
                continue
            cns_name = 'Transf_Roll'
            cns = pb.constraints.get(cns_name)
            if cns:
                cns = pb.constraints.remove(cns)
    
    
def set_facial(enable=True, 
                mouth_enabled=True, auto_lips=True, auto_lips_visual=False, lips_offset=False, lips_corner_offset=False, lips_floor=False, #lips_floor_full=False,
                lips_floor_offset=0.0, lips_amount=2, lips_masters=1, lips_masters_linear=0.0, lips_updt_transf=False, lips_soft_lin_corner=0.0, 
                lips_soft_limit_corner=0, lips_soft_lin_jaw=0.0, lips_soft_limit_jaw=0, jaw_speed=1.0, lips_roll_cns=False, lips_roll_speed=1.0,
                lips_big_masters=False,
                teeth_enabled=True, tongue_enabled=True, eyebrows_type="type_1", 
                eye_l_enabled=True, eye_r_enabled=True,
                eyelids_align=True, eyelid_speed=1.0, eyelids_amount=3, enable_eyelids_tweak=False, eyelids_updt_transf=False,
                skulls_align=True, skull_bones=False, chins_enabled=True, noses_enabled=True,
                eyebrow_l_enabled=True, eyebrow_r_enabled=True, cheeks_enabled=True
                ):
    
    print("\nSetting facial...")
    
    context = bpy.context
    scn = context.scene
    rig_name = bpy.context.active_object.name
    rig = get_object(rig_name)

    xmirror_state = rig.data.use_mirror_x
    rig.data.use_mirror_x = False

    # get the bone side
    side = ".x"

    if len(get_selected_edit_bones()):
        b_name = get_selected_edit_bones()[0].name
        if '_dupli_' in b_name:
            side = b_name[-12:]
            side = side[:-2] + ".x"
    else:
        print("  No bone selected")

    head_ref = get_edit_bone("head_ref" + side)

    if not head_ref and enable:
        print("  head_ref" + side, "does not exist, cannot create facial")
        return
        
    # clamp auto lips limit, cannot be higher than lips amount
    if lips_soft_limit_corner > lips_amount:
        lips_soft_limit_corner = lips_amount
    if lips_soft_limit_jaw > lips_amount:
        lips_soft_limit_jaw = lips_amount
        
    # force lips update if lips amount changed or lips_soft_limit_corner changed
    force_lips_updt_transf = False
    force_lips_updt_soft = False
    
    if 'lips_soft_limit_corner' in head_ref.keys():
        if head_ref['lips_soft_limit_corner'] != lips_soft_limit_corner:
            force_lips_updt_soft = True  
    if 'lips_soft_limit_jaw' in head_ref.keys():
        if head_ref['lips_soft_limit_jaw'] != lips_soft_limit_jaw:
            force_lips_updt_soft = True  
    if 'lips_soft_lin_corner' in head_ref.keys():
        if head_ref['lips_soft_lin_corner'] != lips_soft_lin_corner:
            force_lips_updt_soft = True   
    if 'lips_soft_lin_jaw' in head_ref.keys():
        if head_ref['lips_soft_lin_jaw'] != lips_soft_lin_jaw:
            force_lips_updt_soft = True   
    if 'lips_amount' in head_ref.keys():
        if head_ref['lips_amount'] != lips_amount:
            force_lips_updt_transf = True
    elif lips_amount != 2:# the lips amount was not yet stored and is different from default 2, must be aligned
        force_lips_updt_transf = True
        
        
    # force eyelids transforms update if amount has changed
    force_eyelids_updt_transf = False
    
    if 'eyelids_amount' in head_ref.keys():
        if eyelids_amount != head_ref['eyelids_amount']:
            force_eyelids_updt_transf = True
    

    # save settings in custom props
    head_ref["facial"] = enable
    head_ref["auto_lips"] = auto_lips    
    head_ref['lips_soft_limit_corner'] = lips_soft_limit_corner   
    head_ref['lips_soft_limit_jaw'] = lips_soft_limit_jaw        
    head_ref["auto_lips_visual"] = auto_lips_visual
    head_ref["lips_floor"] = lips_floor
    #head_ref["lips_floor_full"] = lips_floor_full
    head_ref["lips_floor_offset"] = lips_floor_offset
    head_ref['enable_eyelids_tweak'] = enable_eyelids_tweak
    head_ref["eyelids_amount"] = eyelids_amount
    head_ref['eyelids_updt_transf'] = eyelids_updt_transf
    head_ref["eyelid_align_rot"] = eyelids_align
    head_ref["eyelid_speed_fac"] = eyelid_speed
    head_ref["skulls_align"] = skulls_align
    head_ref["skull_bones"] = skull_bones
    head_ref["mouth_enabled"] = mouth_enabled
    head_ref["teeth_enabled"] = teeth_enabled
    head_ref["tongue_enabled"] = tongue_enabled
    head_ref['chins_enabled'] = chins_enabled
    head_ref['noses_enabled'] = noses_enabled
    head_ref['eye_l_enabled'] = eye_l_enabled
    head_ref['eye_r_enabled'] = eye_r_enabled
    head_ref['eyebrow_l_enabled'] = eyebrow_l_enabled
    head_ref['eyebrow_r_enabled'] = eyebrow_r_enabled
    head_ref['cheeks_enabled'] = cheeks_enabled
    head_ref['jaw_speed'] = jaw_speed
    head_ref['lips_amount'] = lips_amount
    head_ref['lips_masters'] = lips_masters
    head_ref['lips_masters_linear'] = lips_masters_linear
    head_ref['lips_roll_cns'] = lips_roll_cns
    head_ref['lips_roll_speed'] = lips_roll_speed
    head_ref['lips_soft_lin_corner'] = lips_soft_lin_corner
    head_ref['lips_soft_lin_jaw'] = lips_soft_lin_jaw
    head_ref['lips_updt_transf'] = lips_updt_transf
    
    
    # disable the proxy picker to avoid bugs
    proxy_picker_state = disable_proxy_picker()

    # Active all layers
    layers_select = enable_all_armature_layers()
    
    
    def create_eye_target_mid(side):
        c_eye_target_name = retarget_bone_side(ard.eye_bones_mid[0], side, dupli_only=True)
        c_eye_target_x = get_edit_bone(c_eye_target_name)
        if c_eye_target_x == None:
            # coords
            c_eye_target_x = create_edit_bone(c_eye_target_name)
            c_eye_target_x.head, c_eye_target_x.tail = [0,0,0], [0,0,1]
            set_bone_layer(c_eye_target_x, 0)
            
            # target sides parent
            for s in ['.l', '.r']:                
                c_eye_target_s_name = retarget_bone_side(c_eye_target_name, side[:-2] + s)
                c_eye_target_s = get_edit_bone(c_eye_target_s_name)                
                if c_eye_target_s:
                    c_eye_target_s.parent = c_eye_target_x
               
            # pose data
            bpy.ops.object.mode_set(mode='POSE')
            
            c_eye_target_pb = get_pose_bone(c_eye_target_name)
            c_eye_target_pb.rotation_mode = 'XYZ'
            set_bone_custom_shape(c_eye_target_pb, 'cs_eye_aim_global')
            set_custom_shape_scale(c_eye_target_pb, 2.0)            
            set_bone_color_group(rig, c_eye_target_pb, 'body.x')            
            
            cns = c_eye_target_pb.constraints.new('CHILD_OF')
            cns.target = rig
            cns.subtarget = retarget_bone_side(ard.heads_dict['deform'], side, dupli_only=True)    

            create_custom_prop(node=c_eye_target_pb, prop_name='eye_target', prop_val=1.0, prop_min=0.0, prop_max=1.0, prop_description="Make the eyes follow the target controllers")
            
            bpy.ops.object.mode_set(mode='EDIT')
            
            
    def delete_eye_target_mid(side):
        c_eye_target_name = retarget_bone_side(ard.eye_bones_mid[0], side, dupli_only=True)
        c_eye_target_x = get_edit_bone(c_eye_target_name)
        if c_eye_target_x:
            delete_edit_bone(c_eye_target_x)
    
    
    def set_facial_sublimb(module_name='', active=None, bones_list=None, matrix_ref_bone=None):
    
        bpy.ops.object.mode_set(mode='EDIT')
        
        if active:            
            limb_exist = False
            # check current bones
            for bname in bones_list:
                final_bname = retarget_bone_side(bname, side, dupli_only=True)
                eb = get_data_bone(final_bname)
                if eb:
                    limb_exist = True
                    #print(' ', module_name, 'exists, do not create')
                    break                    
           
            if not limb_exist:
                print('  Create '+module_name+'...')
                addon_directory = os.path.dirname(os.path.abspath(__file__))
                filepath = addon_directory + "/armature_presets/modules.blend"
           
                # make a list of current custom shapes objects in the scene for removal later
                cs_objects = [obj.name for obj in bpy.data.objects if obj.name.startswith("cs_")]

                # load the objects in the blend file data
                with bpy.data.libraries.load(filepath, link=False) as (data_from, data_to):                 
                    data_to.objects = [i for i in data_from.objects if i == module_name]

                # link in scene
                for obj in data_to.objects:
                    context.scene.collection.objects.link(obj)
                    print("  Linked armature:", obj.name)
                    
                bpy.ops.object.mode_set(mode='OBJECT')

                set_active_object(module_name)
                rig_module = get_object(module_name)
                
                bpy.ops.object.mode_set(mode='POSE')
                
                for pb in rig_module.pose.bones:
                    # replace custom shapes by custom shapes already existing in the scene                
                    if pb.custom_shape:
                        if pb.custom_shape.name not in cs_objects:
                            cs_name = pb.custom_shape.name.replace('.001', '')
                            if cs_name in cs_objects:
                                set_bone_custom_shape(pb, cs_name)
                                
                             
                    # rename bones with new side
                    if "_dupli_" in side:
                        pb.name = retarget_bone_side(pb.name, side, dupli_only=True)                       

                
                # retarget constraints targets side
                for pb in rig_module.pose.bones:                
                    for cns in pb.constraints:
                        # None target object to current armature                    
                        if 'target' in dir(cns):
                            if cns.target == None:
                                cns.target = get_object(rig_name)
                     
                        # Bones (subtarget) default side to current side
                        if 'subtarget' in dir(cns):
                            if cns.subtarget != "":
                                new_subtarget = retarget_bone_side(cns.subtarget, side, dupli_only=True)
                                if new_subtarget != cns.subtarget:# setting the subtarget can lead to constraints update issues. Only set if necessary
                                    cns.subtarget = new_subtarget
                                    
                
                # retarget drivers variables side              
                for dr in rig_module.animation_data.drivers:
                    if dr.data_path.startswith('pose.bones'):
                        if "_dupli_" in side:
                            for var in dr.driver.variables:
                                for tar in var.targets:
                                    tar_pbname = get_pbone_name_from_data_path(tar.data_path)
                                    tar_pbname_retarget = retarget_bone_side(tar_pbname, side, dupli_only=True)
                                    tar.data_path = tar.data_path.replace(tar_pbname, tar_pbname_retarget)
                                    #if not side[:-2] in tar.data_path:                                   
                                    #    tar.data_path = tar.data_path.replace(side[-2:], side[:-2] + side[-2:])
                                 
                
                # find added/useless custom shapes and delete them
                used_shapes = [b.custom_shape.name for b in rig_module.pose.bones if b.custom_shape]
                for obj in bpy.data.objects:
                    if obj.name.startswith('cs_'):
                        if not obj.name in cs_objects and not obj.name in used_shapes:
                            delete_object(obj)
                
                bpy.ops.object.mode_set(mode='OBJECT')
                
                rig_module.matrix_world = rig.matrix_world.copy()
                
                # Merge to the main armature
                bpy.ops.object.select_all(action='DESELECT')
                set_active_object(module_name)
                set_active_object(rig_name)
                bpy.ops.object.mode_set(mode='OBJECT')
                bpy.ops.object.join()

                bpy.ops.object.mode_set(mode='EDIT')
              
                # Move all new facial bones near the head
                b1 = get_edit_bone(retarget_bone_side(matrix_ref_bone, side, dupli_only=True))
                
                if len(b1.keys()):
                    if "arp_offset_matrix" in b1.keys():   
                        
                        head_ref = get_edit_bone(retarget_bone_side(ard.head_ref[0], side, dupli_only=True))
                        b1_local = Matrix(b1["arp_offset_matrix"]) @ b1.matrix

                        # store children bones matrix                          
                        children_mat_dict = {}
                        for child_name in bones_list:
                            eb = get_edit_bone(retarget_bone_side(child_name, side, dupli_only=True))
                            if eb == None:
                                continue
                            children_mat_dict[eb] = b1.matrix.inverted() @ eb.matrix

                        # move b1
                        b1.matrix = head_ref.matrix @ b1_local
                        # move other bones
                        for child_ in children_mat_dict:
                            child_.matrix = b1.matrix @ children_mat_dict[child_]

                        # store current bones coords copy in a new dict to avoid the multiple transform issue when bones have connected parent
                        bones_coords = {}
                        for b in children_mat_dict:
                            bones_coords[b] = b.head.copy(), b.tail.copy()
                        
                        # scale proportionally to the head bone
                        scale_from_origin(ed_bone=b1, center=head_ref.head, factor=(head_ref.tail - head_ref.head).magnitude * 4)

                        for eb in bones_coords:
                            scale_from_origin(ed_bone=eb, center=head_ref.head, head_coords=bones_coords[eb][0],
                                              tail_coords=bones_coords[eb][1],
                                              factor=(head_ref.tail - head_ref.head).magnitude * 4)
                                              
                parent_lost_bones()
                
                # eyes must have a common c_eye_target.x controller
                if module_name == 'rig_eye_l' or module_name == 'rig_eye_r':
                    create_eye_target_mid(side)
                
                # triggers the driver update
                for dr in rig.animation_data.drivers:
                    if dr.data_path.startswith('pose.bones'):
                        dr.driver.expression += ''
                        
                
            # parent root bones
            head_eb = get_edit_bone(retarget_bone_side(ard.heads_dict['deform'], side))
            skull_01_eb = get_edit_bone(retarget_bone_side(ard.skulls_dict['01'], side))
            skull_02_eb = get_edit_bone(retarget_bone_side(ard.skulls_dict['02'], side))
            
            if module_name.startswith('rig_eyebrow_'):
                c_eyebrow_full_name = retarget_bone_side(ard.eyebrow_bones_dict['eyebrow_full']['name']+'.'+module_name[-1:], side, dupli_only=True)               
                c_eyebrow_full = get_edit_bone(c_eyebrow_full_name)
                c_eyebrow_full.parent = skull_02_eb if skull_02_eb else head_eb
            elif module_name.startswith('rig_eye_'):
                c_eyeoffset_name = retarget_bone_side(ard.eye_bones_dict['eye_offset']['name']+'.'+module_name[-1:], side, dupli_only=True)               
                c_eyeoffset = get_edit_bone(c_eyeoffset_name)
                c_eyetrack_name = retarget_bone_side(ard.eye_bones_dict['eye_ref_track']['name']+'.'+module_name[-1:], side, dupli_only=True)               
                c_eye_track = get_edit_bone(c_eyetrack_name)
                eyeoffset_ref_name = retarget_bone_side(ard.eye_bones_ref_dict['eye_offset']+'.'+module_name[-1:], side, dupli_only=True)
                eyeoffset_ref = get_edit_bone(eyeoffset_ref_name)
                if eyeoffset_ref.parent == None:# custom eye offset parent
                    c_eyeoffset.parent = skull_02_eb if skull_02_eb else head_eb
                    c_eye_track.parent = skull_02_eb if skull_02_eb else head_eb
                else:
                    c_eyeoffset.parent = eyeoffset_ref.parent
                    c_eye_track.parent = eyeoffset_ref.parent
                    
            elif module_name.startswith('rig_noses'):
                c_nose_01_name = retarget_bone_side(ard.nose_bones_dict['nose_01']['name'], side)               
                c_nose_01 = get_edit_bone(c_nose_01_name)
                c_nose_03_name = retarget_bone_side(ard.nose_bones_dict['nose_03']['name'], side)               
                c_nose_03 = get_edit_bone(c_nose_03_name)
                c_nose_01.parent = skull_02_eb if skull_02_eb else head_eb
                c_nose_03.parent = skull_02_eb if skull_02_eb else head_eb
            elif module_name.startswith('rig_cheeks'):
                for _s in ['.l', '.r']:
                    c_cheek_smile_name = retarget_bone_side(ard.cheek_bones_dict['cheek_smile']['name']+_s, side, dupli_only=True)
                    c_cheek_smile = get_edit_bone(c_cheek_smile_name)
                    c_cheek_inf_name = retarget_bone_side(ard.cheek_bones_dict['cheek_inflate']['name']+_s, side, dupli_only=True)               
                    c_cheek_inf = get_edit_bone(c_cheek_inf_name)
                    c_cheek_smile.parent = skull_02_eb if skull_02_eb else head_eb
                    c_cheek_inf.parent = skull_02_eb if skull_02_eb else head_eb                
            elif module_name == 'rig_mouth':
                lips_roll_top_name = retarget_bone_side(ard.mouth_bones_dict['c_lips_roll_top']['name'], side)
                lips_roll_top = get_edit_bone(lips_roll_top_name)
                lips_roll_top.parent = skull_01_eb if skull_01_eb else head_eb
            elif module_name == 'rig_teeth':
                teeth_top_name = retarget_bone_side(ard.teeth_bones_dict['teeth_top_master']['name'], side)
                teeth_top = get_edit_bone(teeth_top_name)
                teeth_top.parent = skull_01_eb if skull_01_eb else head_eb            
            
                
        else:# remove facial module
            if module_name == 'rig_mouth':
                bones_list += ard.get_variable_lips(side, type='ALL')
            elif module_name.startswith('rig_eye_'):
                bones_list += ard.get_variable_eyelids(side, type='ALL', eye_sides=['.'+module_name[-1:]])           
            
            for bname in bones_list:   
                final_bname = retarget_bone_side(bname, side, dupli_only=True)
                eb = get_edit_bone(final_bname)
                if eb:
                    delete_edit_bone(eb)
                        
    
    def parent_lost_bones():
        for eb in rig.data.edit_bones:
            bside = get_bone_side(eb.name)            
            if len(eb.keys()) == 0:
                continue
            if not "arp_parent" in eb.keys():
                continue
            
            parent_name = retarget_bone_side(eb["arp_parent"], bside, dupli_only=True)

            if parent_name.startswith("c_skull") and not skull_bones:
                parent_name = retarget_bone_side('head.x', bside, dupli_only=True)     
            
            parent_bone = get_edit_bone(parent_name)

            if eb.parent == None or parent_name.startswith("c_skull"):# make sure to parent to skull bones
                if parent_bone:
                    eb.parent = parent_bone                   
    

    def delete_facial():
        facial_bones = ard.facial_ref + ard.facial_bones + ['c_p_head.x']
        #   multi lips     
        for bname in ard.get_variable_lips(side, type='ALL', no_side=True):            
            facial_bones.append(bname)
        
        #   multi eyelids
        for bname in ard.get_variable_eyelids(side, type='ALL', no_side=True):            
            facial_bones.append(bname)
        
        for _name in facial_bones:
            sides = []                     
            if _name[-2:] == ".x":
                _name = _name.replace(".x", "")
                sides.append(side[:-2] + ".x")
            else:
                sides.append(side[:-2] + ".l")
                sides.append(side[:-2] + ".r")

            for side_2 in sides:
                bo = get_edit_bone(_name + side_2)
                if bo:
                    delete_edit_bone(bo)

        print("  Facial bones deleted.")

        bpy.ops.object.mode_set(mode='OBJECT')
        remove_invalid_drivers()
        bpy.ops.object.mode_set(mode='EDIT')
        
                
    def set_autolips():
        if not mouth_enabled:
            return
            
        sides = [".l", ".r"]

        # set lips corner
        bpy.ops.object.mode_set(mode='POSE')
        
        for _side in sides:
            c_lips_smile_offset_name = 'c_lips_smile_offset'+side[:-2]+_side
            c_lips_smile_name = 'c_lips_smile'+side[:-2]+_side
            c_lips_smile_offset = get_pose_bone(c_lips_smile_offset_name)
            c_lips_smile = get_pose_bone(c_lips_smile_name)
            
            # is the property there?
            if c_lips_smile.get("autolips") == None:
                c_lips_smile["autolips"] = 1.0
            
            if auto_lips == False:
                c_lips_smile["autolips"] = 0.0
            else:
                c_lips_smile["autolips"] = 1.0
            
            # set driver
            for dr in bpy.context.active_object.animation_data.drivers:
                if 'pose.bones' in dr.data_path:
                    b_name = dr.data_path.split('"')[1]
                    if b_name != c_lips_smile_offset_name:
                        continue

                        # is the autolips variable there?
                    autolips_var = dr.driver.variables.get("autolips")
                    if autolips_var == None:
                        autolips_var = dr.driver.variables.new()
                        autolips_var.name = "autolips"
                        autolips_var.type = "SINGLE_PROP"
                        autolips_var.targets[0].id = bpy.context.active_object
                        autolips_var.targets[0].data_path = 'pose.bones["' + c_lips_smile_name + '"]["autolips"]'

                    dr.driver.expression = 'var + autolips'
                    

        def clear_auto_lips(_b, ext):
        
            bpy.ops.object.mode_set(mode='EDIT')
            
            
            follow_bone_name = _b[2:] + "_follow" + ext
            # remove follow bones if any
            follow_bone = get_edit_bone(follow_bone_name)
            if follow_bone:
                delete_edit_bone(follow_bone)

            # reset lips_offset bones drivers
            
            bpy.ops.object.mode_set(mode='POSE')

            offset_bone_name = _b + "_offset" + ext
            offset_bone = get_pose_bone(offset_bone_name)
            cont_bone = get_pose_bone(_b + ext)

            for dr in bpy.context.active_object.animation_data.drivers:
                if 'pose.bones' in dr.data_path:
                    b_name = dr.data_path.split('"')[1]
                    if b_name != offset_bone_name:
                        continue

                    dr.driver.expression = 'var'

            # delete constraints
            cns1 = offset_bone.constraints.get("MoveWithLipsCorner")
            if cns1:
                offset_bone.constraints.remove(cns1)
        
        lips_bones_temp = ['c_lips_top.x', 'c_lips_top', 'c_lips_bot', 'c_lips_bot.x']
        # add multiple lips
        for i in range(1, lips_amount):
            str_idx = '%02d' % i
            lips_bones_temp.append('c_lips_top_'+str_idx)
            lips_bones_temp.append('c_lips_bot_'+str_idx)
            
        lips_bones = []
        
        for b in lips_bones_temp:
            if b.endswith('.x'):
                lips_bones.append(b)
            else:
                for _side in sides:
                    lips_bones.append(b+_side)
            
        
        # clear auto lips first in case it's already enabled
        for b in lips_bones:
            if b.endswith('.x'):
                continue  
            clear_auto_lips(b[:-2], side[:-2]+b[-2:])
                
        #print("  Auto-lips cleared")
        
        bpy.ops.object.mode_set(mode='EDIT')
        
        # set auto lips
        for b in lips_bones:      
            
            ref_bone_name = retarget_bone_side(b[2:][:-2] + '_ref'+b[-2:], side, dupli_only=True)
            cont_bone_name = retarget_bone_side(b, side, dupli_only=True)
            follow_bone_name = retarget_bone_side(b[2:][:-2]+'_follow'+b[-2:], side, dupli_only=True)
            lips_corner_name = retarget_bone_side('c_lips_smile'+b[-2:], side, dupli_only=True)
            
            bpy.ops.object.mode_set(mode='EDIT')
            
            ref_bone = get_edit_bone(ref_bone_name)
            cont_bone = get_edit_bone(cont_bone_name)

            if ref_bone == None:
                print("  Reference bone not found, can't set autolips:", ref_bone_name)
                continue            

            # autolips enabled
            if auto_lips:  
                def create_movewithlipscorner_cns(b, pb, lips_corner_name):                                        
                    cns_name = "MoveWithLipsCorner"
                    cns1 = pb.constraints.get(cns_name)
                    if cns1 == None:
                        cns1 = pb.constraints.new("COPY_LOCATION")
                        cns1.name = cns_name
                        cns1.target = rig
                        cns1.subtarget = lips_corner_name
                        if scn.arp_retro_lips:#backward-compatibility
                            cns1.use_x = False
                        else:
                            cns1.use_x = True
                        cns1.target_space = 'LOCAL'
                        if scn.arp_retro_lips:# backward-compatibility
                            cns1.owner_space = 'LOCAL'
                        else:
                            cns1.owner_space = 'CUSTOM'
                            cns1.space_object = rig
                            cns1.space_subtarget = cns1.subtarget
                            
                        cns1.use_offset = True                    
                    
                    if b.startswith("c_lips_top") or b.startswith("c_lips_bot"):
                        def_limit = lips_amount if lips_soft_limit_corner == 0 else lips_soft_limit_corner    
                        idx = 1
                        if b.startswith('c_lips_top_') or b.startswith('c_lips_bot_'):
                            idx = int(b.split('_')[3].split('.')[0]) +1
                        
                        if scn.arp_retro_lips:# backward-compatibility
                            if idx == 1:
                                autolips_val = 0.2
                            elif idx == 2:
                                autolips_val = 0.4
                        else:
                            fac = 1/(def_limit+1)
                            limit_inv = lips_amount-def_limit
                            val_linear = fac * (idx-limit_inv)                              
                            if val_linear < 0:
                                autolips_val = 0.0
                            else:
                                autolips_val = round_interpolate(val_linear, linear=lips_soft_lin_corner, repeat=1)
                                
                        cns1.influence = autolips_val          
                        
                        
                if auto_lips_visual and not b.endswith('.x'):# move visually only, exclude middle bones
                    
                    # create follow bone
                    follow_bone = get_edit_bone(follow_bone_name)
                    if follow_bone == None:                       
                        follow_bone = create_edit_bone(follow_bone_name)
                        copy_bone_transforms(ref_bone, follow_bone)                            
                        follow_bone.parent = cont_bone                    
                        # set layer
                        set_bone_layer(follow_bone, 8)
                    
                    # Set constraints
                    bpy.ops.object.mode_set(mode='POSE')                    
                    
                    follow_pbone = get_pose_bone(follow_bone_name)
                    
                    # lips corner constraint
                    create_movewithlipscorner_cns(b, follow_pbone, lips_corner_name)                    

                    #   jaw constraint
                    if b.startswith('c_lips_top') or b.startswith('c_lips_bot'):
                        def_limit = lips_amount if lips_soft_limit_jaw == 0 else lips_soft_limit_jaw
                    
                        cns_name = "MoveWithJaw"
                        cns2 = follow_pbone.constraints.get(cns_name)
                        if cns2 == None:
                            cns2 = follow_pbone.constraints.new("COPY_TRANSFORMS")
                            cns2.name = cns_name
                            cns2.target = rig
                            cns2.subtarget = 'c_'+follow_pbone.name.replace('follow', 'retain')
                            cns2.owner_space = cns2.target_space = 'WORLD'
                            move_constraint(follow_pbone, cns2, 'UP', len(follow_pbone.constraints)-1)
                            
                        idx = 1
                        if b.startswith('c_lips_top_') or b.startswith('c_lips_bot_'):
                            idx = int(b.split('_')[3].split('.')[0]) +1
                        if scn.arp_retro_lips:# backward-compatibility
                            if idx == 1:
                                autolips_val = 0.1
                            elif idx == 2:
                                autolips_val = 0.2
                        else:                           
                            fac = 1/(def_limit+1)
                            limit_inv = lips_amount-def_limit
                            val_linear = fac * (idx-limit_inv)                              
                            if val_linear < 0:
                                autolips_val = 0.0
                            else:
                                autolips_val = round_interpolate(val_linear, linear=lips_soft_lin_jaw, repeat=1)
                            
                        cns2.influence = autolips_val
                        '''
                        if cns2 == None:
                            cns2 = follow_pbone.constraints.new("TRANSFORM")
                            cns2.name = cns_name
                            cns2.target = rig
                            cns2.subtarget = "c_jawbone"+side[:-2]+'.x'
                            cns2.use_motion_extrapolate = True
                            cns2.from_max_z = 1
                            cns2.map_to_y_from = "Z"
                            cns2.map_to_z_from = "Y"
                            cns2.to_max_y = -1
                            cns2.target_space = cns2.owner_space = "LOCAL"

                        cns2.influence = 0.2
                        '''
                    # set custom shape transform bone
                    cont_pbone = get_pose_bone(cont_bone_name)
                    cont_pbone.custom_shape_transform = follow_pbone
                    

                if not auto_lips_visual and not b.endswith('.x'):# not autlips visual, move with real deform
                    # remove follow bones if any
                    follow_bone = get_edit_bone(follow_bone_name)
                    if follow_bone:
                        delete_edit_bone(follow_bone)

                    bpy.ops.object.mode_set(mode='POSE')

                    offset_bone_name = b[:-2] + "_offset" + side[:-2] + b[-2:]
                    offset_bone = get_pose_bone(offset_bone_name)
                    cont_bone = get_pose_bone(cont_bone_name)                    
                    
                    # Set auto lips amount when opening jaw                    
                    if b.startswith('c_lips_top') or b.startswith('c_lips_bot'):
                        def_limit = lips_amount if lips_soft_limit_jaw == 0 else lips_soft_limit_jaw
                       
                        if cont_bone.get("autolips") == None or (force_lips_updt_transf or force_lips_updt_soft or lips_updt_transf): 
                            idx = 1
                            if b.startswith('c_lips_top_') or b.startswith('c_lips_bot_'):
                                idx = int(b.split('_')[3].split('.')[0]) +1
                            if scn.arp_retro_lips:# backward-compatibility
                                if idx == 1:
                                    autolips_val = 0.1
                                elif idx == 2:
                                    autolips_val = 0.2
                            else:                           
                                fac = 1/(def_limit+1)
                                limit_inv = lips_amount-def_limit
                                val_linear = fac * (idx-limit_inv)                              
                                if val_linear < 0:
                                    autolips_val = 0.0
                                else:
                                    autolips_val = round_interpolate(val_linear, linear=lips_soft_lin_jaw, repeat=1)
                            
                            cont_bone['autolips'] = autolips_val
                  
                    # set the lips_offset driver
                    for dr in rig.animation_data.drivers:
                        if 'pose.bones' in dr.data_path:
                            b_name = dr.data_path.split('"')[1]
                            if b_name != offset_bone_name:
                                continue
                            autolips_var = dr.driver.variables.get("autolips")
                            if autolips_var == None:
                                autolips_var = dr.driver.variables.new()
                                autolips_var.name = "autolips"
                                autolips_var.type = "SINGLE_PROP"
                                autolips_var.targets[0].id = bpy.context.active_object
                                autolips_var.targets[0].data_path = 'pose.bones["'+cont_bone_name+'"]["autolips"]'

                            dr.driver.expression = 'var + autolips'
                    
                    # set the lips corner constraint
                    create_movewithlipscorner_cns(b, offset_bone, lips_corner_name)
                    
                
                bpy.ops.object.mode_set(mode='POSE')
                    
                offset_name = b[:-2] + "_offset" + side[:-2] + b[-2:]
                offset_pb = get_pose_bone(offset_name)
                
                # sticky lips - floor constraint                
                jaw_ret_name = retarget_bone_side(ard.mouth_bones_dict['jaw_ret_bone']['name'], side)
                c_jaw_name = retarget_bone_side(ard.mouth_bones_dict['c_jawbone']['name'], side)
                c_jaw = get_pose_bone(c_jaw_name)
                cns_ret_name = 'CopyRotSticky'                
                pname = 'lips_sticky_follow'
                
                if lips_floor:                    
                    c_copy = offset_pb.constraints.get('Copy Transforms')
                    
                    # add jaw sticky follow lips prop
                    if not pname in c_jaw.keys():
                        create_custom_prop(node=c_jaw, prop_name=pname, prop_val=0.0, prop_min=0.0, prop_max=1.0, prop_description="The upper lips will follow the lower lips", default=0.0)
                    
                    def set_floor_cns(pb, subt):
                        cns_name = 'Floor'
                        c = pb.constraints.get(cns_name)
                        if c == None:
                            c = pb.constraints.new('FLOOR')
                            c.name = cns_name
                            c.target = rig
                            c.subtarget = subt
                            c.floor_location = 'FLOOR_Y' if 'top' in b else 'FLOOR_NEGATIVE_Y'
                            c.use_rotation = True
                            
                            if len(pb.constraints) > 2:
                                move_constraint(pb, c, 'UP', 1)
                            
                        c.offset = -lips_floor_offset                            
                    
                    def set_floorX_cns(pb, subt):
                        cns_name = 'FloorX'
                        cx = pb.constraints.get(cns_name)
                        if cx == None:
                            cx = pb.constraints.new('FLOOR')
                            cx.name = cns_name
                            cx.target = rig
                            cx.subtarget = subt
                            cx.floor_location = 'FLOOR_NEGATIVE_X' if b.endswith('.r') else 'FLOOR_X'
                            cx.use_rotation = True
                        
                            if len(pb.constraints) > 2:                            
                                move_constraint(pb, cx, 'UP', 1) 
                        
                        cx.offset = lips_floor_offset if pb.name.endswith('.r') else -lips_floor_offset
                            
                    def set_floorPosZ_cns(pb, subt):
                        cns_name = 'FloorPosZ'
                        cz_pos = pb.constraints.get(cns_name)
                        if cz_pos == None:
                            cz_pos = pb.constraints.new('FLOOR')
                            cz_pos.name = cns_name
                            cz_pos.target = rig
                            cz_pos.subtarget = subt
                            cz_pos.floor_location = 'FLOOR_Z'
                            cz_pos.use_rotation = True
                            
                            if len(pb.constraints) > 2:                            
                                move_constraint(pb, cz_pos, 'UP', 1)
                        
                        add_driver_to_prop(rig, 'pose.bones["'+pb.name+'"].constraints["'+cns_name+'"].influence', 'pose.bones["'+c_jaw_name+'"]["'+pname+'"]', array_idx=-1, exp="var")
                    
                    def set_floorNegZ_cns(pb, subt):
                        cns_name = 'FloorNegZ'
                        cz_neg = pb.constraints.get(cns_name)
                        if cz_neg == None:
                            cz_neg = pb.constraints.new('FLOOR')
                            cz_neg.name = cns_name
                            cz_neg.target = rig
                            cz_neg.subtarget = subt
                            cz_neg.floor_location = 'FLOOR_NEGATIVE_Z'
                            cz_neg.use_rotation = True
                            
                            if len(pb.constraints) > 2:                            
                                move_constraint(pb, cz_neg, 'UP', 1)
                                
                        add_driver_to_prop(rig, 'pose.bones["'+pb.name+'"].constraints["'+cns_name+'"].influence', 'pose.bones["'+c_jaw_name+'"]["'+pname+'"]', array_idx=-1, exp="var")
                            
                    
                    if 'top' in b:# or ('bot' in b and lips_floor_full):                        
                        set_floor_cns(offset_pb, c_copy.subtarget)                    
                        set_floorX_cns(offset_pb, c_copy.subtarget)                        
                        set_floorPosZ_cns(offset_pb, c_copy.subtarget)
                        set_floorNegZ_cns(offset_pb, c_copy.subtarget)
                        
                    
                    if 'bot' in b:# and lips_floor_full == False:# remove floor cns for bottom only
                        c = offset_pb.constraints.get('Floor')
                        if c:
                            offset_pb.constraints.remove(c)
                        cx = offset_pb.constraints.get('FloorX')
                        if cx:
                            offset_pb.constraints.remove(cx)
                        
                    # jew_ret constraint                    
                    jaw_ret = get_pose_bone(jaw_ret_name)                    
                    copy_rot_cns = jaw_ret.constraints.get(cns_ret_name)
                    
                    if copy_rot_cns == None:                        
                        copy_rot_cns = jaw_ret.constraints.new('COPY_ROTATION')
                        copy_rot_cns.name = cns_ret_name
                        copy_rot_cns.target = rig
                        copy_rot_cns.subtarget = retarget_bone_side(ard.mouth_bones_dict['jawbone']['name'], side)
                        copy_rot_cns.use_y = copy_rot_cns.use_z = False
                        copy_rot_cns.mix_mode = 'ADD'
                        copy_rot_cns.target_space = copy_rot_cns.owner_space = 'LOCAL'
                        add_driver_to_prop(rig, 'pose.bones["'+jaw_ret_name+'"].constraints["'+cns_ret_name+'"].influence', 'pose.bones["'+c_jaw_name+'"].location[2]', array_idx=-1, exp="0 if var > 0 else 0.5")
                    
                else:# remove sticky/floor
                    # constraints
                    cns_names = ['Floor', 'FloorX', 'FloorPosZ', 'FloorNegZ']
                    for cns_name in cns_names:
                        c = offset_pb.constraints.get(cns_name)
                        if c:
                            offset_pb.constraints.remove(c)
                        
                    # remove prop
                    if pname in c_jaw.keys():
                        del c_jaw[pname]
                    
                    # jaw_ret
                    jaw_ret = get_pose_bone(jaw_ret_name)                    
                    copy_rot_cns = jaw_ret.constraints.get(cns_ret_name)
                    
                    if copy_rot_cns:      
                        jaw_ret.constraints.remove(copy_rot_cns)
                        
                  
            # autolips disabled
            else:
                clear_auto_lips(b[:-2], side[:-2]+b[-2:])    
   
    
    def set_lips_amount():
        if not mouth_enabled:
            return
            
        print("  Set lips amount", lips_amount)
        
        bpy.ops.object.mode_set(mode='EDIT')
            
        head_side = side[:-2]
        
        # Create Ref bones
        lips_top_ref_name = 'lips_top_ref'+head_side+'.x'
        lips_bot_ref_name = 'lips_bot_ref'+head_side+'.x'
        lips_top_ref = get_edit_bone(lips_top_ref_name)
        lips_bot_ref = get_edit_bone(lips_bot_ref_name)
        
        created_ref_bones = []
        removed_ref_bones = []
        roll_speed_dict = {}
            
        for _side in ['.l', '.r']:
            lips_corner_name = 'lips_smile_ref'+head_side+_side
            lips_corner = get_edit_bone(lips_corner_name)
            up_vec = lips_corner.head - lips_top_ref.head
            up_vec_div = up_vec / (lips_amount+1)
            bot_vec = lips_corner.head - lips_bot_ref.head
            bot_vec_div = bot_vec / (lips_amount+1)
            
            for i in range(1, 32):            
                str_idx = '%02d' % i
                mode = 'add' if i < lips_amount else 'remove'
                
                for lvl in ['top', 'bot']:                    
                    ref_base_name = 'lips_'+lvl+'_ref'+head_side+'.x'  
                    ref_first_name =  'lips_'+lvl+'_ref'+head_side+_side
                    ref_base_eb = get_edit_bone(ref_base_name)
                    ref_base_vec = ref_base_eb.tail-ref_base_eb.head
                    ref_name = 'lips_'+lvl+'_'+str_idx+'_ref'+head_side+_side                  
                    ref_eb = get_edit_bone(ref_name)                    
                     
                    if mode == 'add':   
                        created_ref_bones.append(ref_name)
                        found = True
                        if ref_eb == None:
                            found = False
                            ref_eb = create_edit_bone(ref_name)                           
                            set_bone_layer(ref_eb, 17)
                            
                        #   align 
                        if (found and (lips_updt_transf or force_lips_updt_transf)) or not found: 
                            bones_to_align = [ref_eb]
                            if i == 1:
                                bones_to_align.append(get_edit_bone(ref_first_name))# the very first lip is handled as an extra
                            for eb in bones_to_align:
                                vec_div = up_vec_div if lvl == 'top' else bot_vec_div
                                lips_base = lips_top_ref if lvl == 'top' else lips_bot_ref                                
                                add = 0 if eb.name == ref_first_name else 1# first lip
                                p_linear = lips_base.head + (vec_div*(i+add)) 
                                p_proj = project_point_onto_plane(p_linear, lips_base.head, lips_base.y_axis)# y
                                p_proj = project_point_onto_plane(p_proj, lips_base.head, lips_base.x_axis)
                                fac = i/(lips_amount+0)                            
                                eb.head = (p_linear * fac) + (p_proj * (1-fac))
                                eb.tail = eb.head + ref_base_vec
                                eb.roll = ref_base_eb.roll
                                if _side == '.r':
                                    eb.roll += math.radians(180)
                                
                    elif mode == 'remove':
                        removed_ref_bones.append(ref_name)                        
                        if ref_eb:                            
                            delete_edit_bone(ref_eb)                            
        
                            
        # Create Offset bones
        created_offset_bones = []
        c_skull_eb = get_edit_bone(ard.skulls_dict['01'])
        head_eb = get_edit_bone(ard.heads_dict['deform'])
        
        #   add
        for ref_name in created_ref_bones:
            offset_name = 'c_'+ref_name.replace('_ref', '_offset')         
            ref_eb = get_edit_bone(ref_name)            
            offset_eb = get_edit_bone(offset_name)
            created_offset_bones.append(offset_name)
            
            if offset_eb == None:
                offset_eb = create_edit_bone(offset_name)                
                set_bone_layer(offset_eb, 8)
                
            copy_bone_transforms(ref_eb, offset_eb)# setting transforms is not relevant here, done when Match to Rig            
            offset_eb.parent = c_skull_eb if c_skull_eb else head_eb
            
            # roll speed prop
            if not 'roll_speed' in ref_eb.keys():
                ref_eb['roll_speed'] = 1.0
            roll_speed_dict['c_'+ref_name.replace('_ref', '')] = ref_eb['roll_speed']
            
        # add roll speed prop to mid lips
        for ref_name in [lips_top_ref_name, lips_bot_ref_name, 
                        'lips_top_ref'+head_side+'.l', 'lips_top_ref'+head_side+'.r',
                        'lips_bot_ref'+head_side+'.l', 'lips_bot_ref'+head_side+'.r']:
            ref_eb = get_edit_bone(ref_name)
            if not 'roll_speed' in ref_eb.keys():
                ref_eb['roll_speed'] = 1.0
            roll_speed_dict['c_'+ref_name.replace('_ref', '')] = ref_eb['roll_speed']
            
        #   remove
        for ref_name in removed_ref_bones:
            offset_name = 'c_'+ref_name.replace('_ref', '_offset')         
            offset_eb = get_edit_bone(offset_name)  
            if offset_eb:
                delete_edit_bone(offset_eb)
                
        
        # Create Retain bones
        #   add
        for ref_name in created_ref_bones:
            retain_name = 'c_'+ref_name.replace('_ref', '_retain')         
            ref_eb = get_edit_bone(ref_name)            
            retain_eb = get_edit_bone(retain_name)
            if retain_eb == None:
                retain_eb = create_edit_bone(retain_name)                
                set_bone_layer(retain_eb, 8)
                
            retain_eb.parent = get_edit_bone('jaw_ret_bone'+head_side+'.x')
            
            copy_bone_transforms(ref_eb, retain_eb)# setting transforms is not relevant here, done when Match to Rig
            
        #   remove
        for ref_name in removed_ref_bones:
            retain_name = 'c_'+ref_name.replace('_ref', '_retain')         
            retain_eb = get_edit_bone(retain_name)  
            if retain_eb:
                delete_edit_bone(retain_eb)
                
        # Remove follow bones (created when setting soft lips only)
        for ref_name in removed_ref_bones:
            follow_name = ref_name.replace('_ref', '_follow')         
            follow_eb = get_edit_bone(follow_name)  
            if follow_eb:
                delete_edit_bone(follow_eb)
                
                
        # Create Control bones
        created_control_bones = []
        
        #   add
        for ref_name in created_ref_bones:
            c_name = 'c_'+ref_name.replace('_ref', '') 
            offset_name = 'c_'+ref_name.replace('_ref', '_offset')
            ref_eb = get_edit_bone(ref_name)            
            c_eb = get_edit_bone(c_name)
            created_control_bones.append(c_name)
            if c_eb == None:
                c_eb = create_edit_bone(c_name, deform=True)                
                set_bone_layer(c_eb, 0)
                
            c_eb.parent = get_edit_bone(offset_name)            
            copy_bone_transforms(ref_eb, c_eb)# setting transforms is not relevant here, done when Match to Rig
            
        #   remove
        for ref_name in removed_ref_bones:
            c_name = 'c_'+ref_name.replace('_ref', '')  
            c_eb = get_edit_bone(c_name)  
            if c_eb:
                delete_edit_bone(c_eb)
        
        
        bpy.ops.object.mode_set(mode='POSE')
        
        
        # Set Constraints   
        #   Offsets
        for offset_name in created_offset_bones:
            
            offset_pb = get_pose_bone(offset_name)
            # Copy Transforms
            cns_name = 'Copy Transforms'
            cns = offset_pb.constraints.get(cns_name)
            if cns == None:
                cns = offset_pb.constraints.new('COPY_TRANSFORMS')
                cns.name = cns_name
            cns.target = rig
            cns.subtarget = offset_name.replace('_offset', '_retain')
            cns.mix_mode = 'REPLACE'
            cns.target_space = cns.owner_space = 'WORLD'
            # driver
            str_idx = offset_name.split('_')[3]
            cont_name = offset_name.replace('_offset', '')
            cns_inf_dp = 'pose.bones["'+offset_name+'"].constraints["'+cns_name+'"].influence'
            tar_dp_dict = {'var': 'pose.bones["c_jawbone'+head_side+'.x"]["lips_retain"]' , 'autolips': 'pose.bones["'+cont_name+'"]["autolips"]'}
            add_driver_to_prop(rig, cns_inf_dp, tar_dp_dict, array_idx=-1, exp="var", multi_var=True)# do not set exp='var + autolips', this is handled by set_auto_lips() on or off
            
        # Roll constraints
        # done when Match to Rig, disable
        #set_lips_roll_constraints(rig, head_side, enable=lips_roll_cns, global_speed=lips_roll_speed, speed_dict=roll_speed_dict)
        
        # Set control display
        for c_name in created_control_bones:            
            c_pb = get_pose_bone(c_name)
            # shape
            if c_pb.custom_shape == None:
                set_bone_custom_shape(c_pb, 'cs_sphere')
                set_custom_shape_scale(c_pb, 1.9)
            # color group
            if c_pb.bone_group == None:
                set_bone_color_group(rig, c_pb, 'body'+c_pb.name[-2:])
            # rot mode
            c_pb.rotation_mode = 'XYZ'
            
        # Set lips corner shapes
        for _side in ['.l', '.r']:
            c_smile_name = ard.mouth_bones_dict['c_lips_smile']['name']+head_side+_side
            c_smile_pb = get_pose_bone(c_smile_name)
            set_shape = False
            if c_smile_pb.custom_shape:
                if not c_smile_pb.custom_shape.name.startswith('cs_user'):
                    set_shape = True
            else:
                set_shape = True
            if set_shape:
                set_bone_custom_shape(c_smile_pb, 'cs_user_c_lips_smile'+_side)
               
        
        bpy.ops.object.mode_set(mode='POSE')
        
        
    # end set_lips_amount()
    
    def set_lips_masters():
        if not mouth_enabled:
            return
            
        print("  Set lips masters...")
        head_side = side[:-2]
        
        bpy.ops.object.mode_set(mode='EDIT')
        #print("  Clear out of range...")
        # clear out of range masters
        for lvl in ['top', 'bot']:          
            for i in range(lips_amount, 33):
                str_idx = '%02d' % i
                # sides
                for _side in ['.l', '.r']:                 
                    master_name = 'c_lips_'+lvl+'_'+str_idx+'_master'+head_side+_side                             
                    master_eb = get_edit_bone(master_name)
                    if master_eb:
                        delete_edit_bone(master_eb)
                        
        bpy.ops.object.mode_set(mode='POSE')        
        
        # clear all master constraints
        #print("  Clear constraints...")
        for i in range(0, 33):
            str_idx = '%02d' % i
            for _side in ['.l', '.r']: 
                for lvl in ['top', 'bot']:
                    offset_name = 'c_lips_'+lvl+'_'+str_idx+'_offset'+head_side+_side
                    if i == 0:
                        offset_name = 'c_lips_'+lvl+'_offset'+head_side+_side
                        
                    offset_pb = get_pose_bone(offset_name)
                    if offset_pb:
                        cns_pre = offset_pb.constraints.get('CopyTransf_MASTER_PRE')
                        if cns_pre:
                            offset_pb.constraints.remove(cns_pre)
                        cns_nxt = offset_pb.constraints.get('CopyTransf_MASTER_NXT')
                        if cns_nxt:
                            offset_pb.constraints.remove(cns_nxt)
                            
        bpy.ops.object.mode_set(mode='EDIT')
        
        created_masters = []
        #print("  Create masters...")
        # create masters
        for lvl in ['top', 'bot']:
            # mid
            c_name = 'c_lips_'+lvl+head_side+'.x'
            c_eb = get_edit_bone(c_name)
            offset_name = 'c_lips_'+lvl+'_offset'+head_side+'.x'
            offset_eb = get_edit_bone(offset_name)
            master_name = 'c_lips_'+lvl+'_master'+head_side+'.x' 
            masterx_eb = get_edit_bone(master_name)
            if lips_masters == 1:
                if masterx_eb:
                    delete_edit_bone(masterx_eb)
            else:
                if masterx_eb == None:
                    masterx_eb = create_edit_bone(master_name)
                    created_masters.append(master_name)
                    masterx_eb.parent = offset_eb
                    c_eb.parent = masterx_eb                       
                    set_bone_layer(masterx_eb, 0)
                    
                copy_bone_transforms(offset_eb, masterx_eb)
                masterx_eb.tail = masterx_eb.head + (masterx_eb.tail-masterx_eb.head)*1.2     
                
            # sides
            for i in range(1, lips_amount):                
                str_idx = '%02d' % i
                for _side in ['.l', '.r']:
                    master_name = 'c_lips_'+lvl+'_'+str_idx+'_master'+head_side+_side
                    master_eb = get_edit_bone(master_name)
                    if (i+1) % lips_masters != 0 or lips_masters == 1:# remove if invalid index (in between two masters) or no masters
                        if master_eb:
                            delete_edit_bone(master_eb)
                        continue
                    c_name = 'c_lips_'+lvl+'_'+str_idx+head_side+_side                    
                    c_eb = get_edit_bone(c_name)
                    offset_name = 'c_lips_'+lvl+'_'+str_idx+'_offset'+head_side+_side
                    offset_eb = get_edit_bone(offset_name)
                    if master_eb == None:
                        master_eb = create_edit_bone(master_name)
                        created_masters.append(master_name)
                    c_eb.parent = master_eb
                    master_eb.parent = offset_eb
                    set_bone_layer(master_eb, 0)
                    
                    copy_bone_transforms(offset_eb, master_eb)
                    master_eb.tail = master_eb.head + (master_eb.tail-master_eb.head)*1.2
                    
                    
        bpy.ops.object.mode_set(mode='POSE')
        
        # set constraints     
        #print("  Create constraints...")
        for lvl in ['top', 'bot']:
            # sides
            for i in range(0, lips_amount):
                if (i+1) % lips_masters == 0:
                    continue
                str_idx = '%02d' % i
                for _side in ['.l', '.r']:     
                    offset_name = 'c_lips_'+lvl+'_'+str_idx+'_offset'+head_side+_side
                    if i == 0:
                        offset_name = 'c_lips_'+lvl+'_offset'+head_side+_side
                     
                    # get prev/next master idx and names
                    prev_idx = ((i//lips_masters)*lips_masters)-1
                    next_idx = prev_idx + lips_masters
                    dist_to_prev = abs(prev_idx-i)
                    dist_to_next = abs(next_idx-i)    
                    str_prev_idx = '%02d' % prev_idx
                    str_next_idx = '%02d' % next_idx
                   
                    prev_master_name = 'c_lips_'+lvl+'_'+str_prev_idx+'_master'+head_side+_side
                    if str_prev_idx == '-1':
                        prev_master_name = 'c_lips_'+lvl+'_master'+head_side+'.x'
                    next_master_name = 'c_lips_'+lvl+'_'+str_next_idx+'_master'+head_side+_side
                        
                    offset_pb = get_pose_bone(offset_name)  
                    
                    # constraint master prev                   
                    cns_name = 'CopyTransf_MASTER_PRE'
                    cns = offset_pb.constraints.get(cns_name)
                    if cns == None:
                        cns = offset_pb.constraints.new('COPY_TRANSFORMS')
                        cns.name = cns_name
                    
                    cns.target = rig
                    cns.subtarget = prev_master_name                    
                    cns.mix_mode = 'BEFORE'
                    cns.target_space = 'LOCAL'
                    cns.owner_space = 'CUSTOM'
                    cns.space_object = rig
                    cns.space_subtarget = cns.subtarget
                    cns.influence = smooth_interpolate((1/lips_masters) * (lips_masters-dist_to_prev), linear=lips_masters_linear)
                    move_constraint(offset_pb, cns, 'DOWN', len(offset_pb.constraints))
                    
                    # constraint master next
                    if get_data_bone(next_master_name):# the last next master may be inexistent depending on index/frequency
                        cns_name = 'CopyTransf_MASTER_NXT'
                        cns = offset_pb.constraints.get(cns_name)
                        if cns == None:
                            cns = offset_pb.constraints.new('COPY_TRANSFORMS')
                            cns.name = cns_name
                        
                        cns.target = rig
                        cns.subtarget = next_master_name
                        cns.mix_mode = 'BEFORE'
                        cns.target_space = 'LOCAL'
                        cns.owner_space = 'CUSTOM'
                        cns.space_object = rig
                        cns.space_subtarget = cns.subtarget
                        cns.influence = smooth_interpolate((1/lips_masters) * (lips_masters-dist_to_next), linear=lips_masters_linear)
                        move_constraint(offset_pb, cns, 'DOWN', len(offset_pb.constraints))
                        
                    
        #print("  Set pbone settings...")
        # set pose bone settings
        for master_n in created_masters:
            master_pb = get_pose_bone(master_n)
            master_pb.rotation_mode = 'XYZ'
            if master_pb.custom_shape == None:
                set_bone_custom_shape(master_pb, 'cs_lip_master')
            if master_pb.bone_group == None:
                set_bone_color_group(rig, master_pb, 'yellow')
        
        
        
        bpy.ops.object.mode_set(mode='EDIT')    
        #print("  Done")
        
    
    def set_eyebrows_type2():
        print("\n  Setting eyebrows type 2...")

        bpy.ops.object.mode_set(mode='EDIT')
        head_side = side[:-2]

        for lat_side in [head_side + ".l", head_side + ".r"]:

            bpy.ops.object.mode_set(mode='EDIT')

            eyebrows_list = ["c_eyebrow_01_end" + lat_side, "c_eyebrow_01" + lat_side, "c_eyebrow_02" + lat_side,
                             "c_eyebrow_03" + lat_side]

            # parent controllers to head
            for eyeb_name in eyebrows_list:
                eyeb = get_edit_bone(eyeb_name)
                eyeb.parent = get_edit_bone("head" + side)

            # add eyebrow_01_end
            eyebrow_01_end_name = "eyebrow_01_end" + lat_side
            eyebrow_01_end = get_edit_bone(eyebrow_01_end_name)
            if eyebrow_01_end == None:
                eyebrow_01_end = create_edit_bone(eyebrow_01_end_name)
                #print("  created", eyebrow_01_end_name)

            c_eyeb_01_end = get_edit_bone(eyebrows_list[0])
            eyebrow_01_end.head = c_eyeb_01_end.head
            eyebrow_01_end.tail = c_eyeb_01_end.tail
            # parent it
            eyebrow_01_end.parent = c_eyeb_01_end
            # set layer
            set_bone_layer(eyebrow_01_end, 8)
            # enable deform
            eyebrow_01_end.use_deform = True
            c_eyeb_01_end.use_deform = False

            # add eyebrow_01_end_target
            eyebrow_01_end_target_name = "eyebrow_01_end_target" + lat_side
            eyebrow_01_end_target = get_edit_bone(eyebrow_01_end_target_name)
            if eyebrow_01_end_target == None:
                eyebrow_01_end_target = bpy.context.active_object.data.edit_bones.new(eyebrow_01_end_target_name)
                print("  created", eyebrow_01_end_target_name)

            c_eyeb_01 = get_edit_bone(eyebrows_list[1])
            eyebrow_01_end_target.head = c_eyeb_01.head
            eyebrow_01_end_target.tail = c_eyeb_01.tail
            # parent it
            eyebrow_01_end_target.parent = c_eyeb_01
            # set layer
            set_bone_layer(eyebrow_01_end_target, 8)
            # disable deform
            eyebrow_01_end_target.use_deform = False

            ### Set Constraints
            bpy.ops.object.mode_set(mode='POSE')

            # c_eyebrow_01_end
            c_eyeb_01_end_pbone = get_pose_bone(eyebrows_list[0])

            # delete existing constraints if they are too many or not enough
            if len(c_eyeb_01_end_pbone.constraints) != 2:
                for cn in c_eyeb_01_end_pbone.constraints:
                    print("  removed invalid constraint", c_eyeb_01_end_pbone.name, cn.name)
                    c_eyeb_01_end_pbone.constraints.remove(cn)

                    # tranformation constraint
            cns_transf = c_eyeb_01_end_pbone.constraints.get("Transformation")
            if cns_transf == None:
                cns_transf = c_eyeb_01_end_pbone.constraints.new("TRANSFORM")
                print("  created constraint", c_eyeb_01_end_pbone.name, "Tranformation")
                cns_transf.name = "Transformation"
                cns_transf.target = bpy.context.active_object
                cns_transf.subtarget = "c_eyebrow_full" + lat_side
                cns_transf.use_motion_extrapolate = True
                cns_transf.map_from = cns_transf.map_to = "LOCATION"
                cns_transf.from_min_x = -1
                cns_transf.from_max_x = 1
                cns_transf.from_min_y = -1
                cns_transf.from_max_y = 1
                cns_transf.from_min_z = cns_transf.from_max_z = 0
                cns_transf.map_to_x_from = 'Z'
                cns_transf.map_to_y_from = 'Y'
                cns_transf.map_to_z_from = 'X'
                cns_transf.to_min_x = cns_transf.to_max_x = 0
                fac = 1
                if lat_side.endswith(".r"):
                    fac = -1
                cns_transf.to_min_y = -1
                cns_transf.to_max_y = 1
                cns_transf.to_min_z = 1 * fac
                cns_transf.to_max_z = -1 * fac
                cns_transf.target_space = cns_transf.owner_space = "LOCAL"

                # damped track constraint
            cns_dt = c_eyeb_01_end_pbone.constraints.get("Damped Track")
            if cns_dt == None:
                cns_dt = c_eyeb_01_end_pbone.constraints.new("DAMPED_TRACK")
                print("  created constraint", c_eyeb_01_end_pbone.name, "Damped Track")
                cns_dt.name = "Damped Track"
                cns_dt.influence = 0.6
                cns_dt.target = bpy.context.active_object
                cns_dt.subtarget = "eyebrow_01_end_target" + lat_side
                cns_dt.track_axis = "TRACK_Z"

            # c_eyebrow_01
            c_eyeb_01_pbone = get_pose_bone(eyebrows_list[1])

            # delete existing constraints if they are too many or not enough
            if len(c_eyeb_01_pbone.constraints) != 1:
                for cn in c_eyeb_01_pbone.constraints:
                    print("  removed invalid constraint", c_eyeb_01_pbone.name, cn.name)
                    c_eyeb_01_pbone.constraints.remove(cn)

                    # tranformation constraint
            cns_transf = c_eyeb_01_pbone.constraints.get("Transformation")
            if cns_transf == None:
                cns_transf = c_eyeb_01_pbone.constraints.new("TRANSFORM")
                print("  created constraint", c_eyeb_01_pbone.name, "Transformation")
                cns_transf.name = "Transformation"
                cns_transf.target = bpy.context.active_object
                cns_transf.subtarget = "c_eyebrow_full" + lat_side
                cns_transf.use_motion_extrapolate = True
                cns_transf.map_from = cns_transf.map_to = "LOCATION"
                cns_transf.from_min_x = -1
                cns_transf.from_max_x = 1
                cns_transf.from_min_y = -1
                cns_transf.from_max_y = 1
                cns_transf.from_min_z = cns_transf.from_max_z = 0
                cns_transf.map_to_x_from = 'Z'
                cns_transf.map_to_y_from = 'Y'
                cns_transf.map_to_z_from = 'X'
                fac = 1
                if lat_side.endswith(".r"):
                    fac = -1
                cns_transf.to_min_x = cns_transf.to_max_x = 0
                cns_transf.to_min_y = -1
                cns_transf.to_max_y = 1
                cns_transf.to_min_z = 1 * fac
                cns_transf.to_max_z = -1 * fac
                cns_transf.target_space = cns_transf.owner_space = "LOCAL"

            # c_eyebrow_02
            c_eyeb_02_pbone = get_pose_bone(eyebrows_list[2])

            # delete existing constraints if they are too many or not enough
            if len(c_eyeb_02_pbone.constraints) != 1:
                for cn in c_eyeb_02_pbone.constraints:
                    print("  removed invalid constraint", c_eyeb_02_pbone.name, cn.name)
                    c_eyeb_02_pbone.constraints.remove(cn)

                    # tranformation constraint
            cns_transf = c_eyeb_02_pbone.constraints.get("Transformation")
            if cns_transf == None:
                cns_transf = c_eyeb_02_pbone.constraints.new("TRANSFORM")
                print("  created constraint", c_eyeb_02_pbone.name, "Transformation")
                cns_transf.name = "Transformation"
                cns_transf.target = bpy.context.active_object
                cns_transf.subtarget = "c_eyebrow_full" + lat_side
                cns_transf.use_motion_extrapolate = True
                cns_transf.map_from = cns_transf.map_to = "LOCATION"
                cns_transf.from_min_x = -1
                cns_transf.from_max_x = 1
                cns_transf.from_min_y = -1
                cns_transf.from_max_y = 1
                cns_transf.from_min_z = cns_transf.from_max_z = 0
                cns_transf.map_to_x_from = 'Z'
                cns_transf.map_to_y_from = 'Y'
                cns_transf.map_to_z_from = 'X'
                fac = 1
                if lat_side.endswith(".r"):
                    fac = -1
                cns_transf.to_min_x = cns_transf.to_max_x = 0
                cns_transf.to_min_y = -0.75
                cns_transf.to_max_y = 0.75
                cns_transf.to_min_z = 0.75 * fac
                cns_transf.to_max_z = -0.75 * fac
                cns_transf.target_space = cns_transf.owner_space = "LOCAL"

            # c_eyebrow_03
            c_eyeb_03_pbone = get_pose_bone(eyebrows_list[3])

            # delete existing constraints if they are too many or not enough
            if len(c_eyeb_03_pbone.constraints) != 1:
                for cn in c_eyeb_03_pbone.constraints:
                    print("  removed invalid constraint", c_eyeb_03_pbone.name, cn.name)
                    c_eyeb_03_pbone.constraints.remove(cn)

                    # tranformation constraint
            cns_transf = c_eyeb_03_pbone.constraints.get("Transformation")
            if cns_transf == None:
                cns_transf = c_eyeb_03_pbone.constraints.new("TRANSFORM")
                print("  created constraint", c_eyeb_03_pbone.name, "Transformation")
                cns_transf.name = "Transformation"
                cns_transf.target = bpy.context.active_object
                cns_transf.subtarget = "c_eyebrow_full" + lat_side
                cns_transf.use_motion_extrapolate = False
                cns_transf.map_from = cns_transf.map_to = "LOCATION"
                if lat_side.endswith(".l"):
                    cns_transf.from_min_x = 0
                    cns_transf.from_max_x = 1
                else:
                    cns_transf.from_min_x = -1
                    cns_transf.from_max_x = 0
                cns_transf.from_min_y = -1
                cns_transf.from_max_y = 1
                cns_transf.from_min_z = cns_transf.from_max_z = 0
                cns_transf.map_to_x_from = 'Z'
                cns_transf.map_to_y_from = 'Y'
                cns_transf.map_to_z_from = 'X'
                cns_transf.to_min_x = cns_transf.to_max_x = 0
                cns_transf.to_min_y = -0.3
                cns_transf.to_max_y = 0.3
                if lat_side.endswith(".l"):
                    cns_transf.to_min_z = 0
                    cns_transf.to_max_z = -0.3
                else:
                    cns_transf.to_min_z = -0.3
                    cns_transf.to_max_z = 0
                cns_transf.target_space = cns_transf.owner_space = "LOCAL"

    
    def set_eyebrows_type3():
        print("\n  Setting eyebrows type 3...")

        bpy.ops.object.mode_set(mode='EDIT')
        head_side = side[:-2]

        for lat_side in [head_side + ".l", head_side + ".r"]:

            bpy.ops.object.mode_set(mode='EDIT')

            # add c_eyebrow_offset_full
            c_eyeb_full_name = 'c_eyebrow_full' + lat_side
            c_eyeb_full = get_edit_bone(c_eyeb_full_name)

            c_eyeb_offset_full_name = 'c_eyebrow_offset_full' + lat_side
            c_eyeb_offset_full = get_edit_bone(c_eyeb_offset_full_name)
            if c_eyeb_offset_full == None:
                c_eyeb_offset_full = bpy.context.active_object.data.edit_bones.new(c_eyeb_offset_full_name)
                c_eyeb_offset_full.head, c_eyeb_offset_full.tail, c_eyeb_offset_full.roll = c_eyeb_full.head.copy(), c_eyeb_full.tail.copy(), c_eyeb_full.roll
                c_eyeb_offset_full.parent = c_eyeb_full
                c_eyeb_offset_full.use_deform = False
                set_bone_layer(c_eyeb_offset_full, 0)

            # add offset for each main bone
            eyebrows_list = ["c_eyebrow_01_end" + lat_side, "c_eyebrow_01" + lat_side, "c_eyebrow_02" + lat_side,
                             "c_eyebrow_03" + lat_side]

            for eyeb_main_name in eyebrows_list:
                eyeb_main = get_edit_bone(eyeb_main_name)
                eyeb_offset_main = get_edit_bone(eyeb_main_name.replace(lat_side, '_offset' + lat_side))
                if eyeb_offset_main == None:
                    eyeb_offset_main = bpy.context.active_object.data.edit_bones.new(
                        eyeb_main_name.replace(lat_side, '_offset' + lat_side))
                    eyeb_offset_main.head, eyeb_offset_main.tail, eyeb_offset_main.roll = eyeb_main.head.copy(), eyeb_main.tail.copy(), eyeb_main.roll
                    set_bone_layer(eyeb_offset_main, 0)
                    eyeb_offset_main.parent = c_eyeb_offset_full

            bpy.ops.object.mode_set(mode='POSE')

            c_eyeb_full_pbone = get_pose_bone(c_eyeb_full_name)
            c_eyeb_offset_full_pbone = get_pose_bone(c_eyeb_offset_full_name)

            # custom shape
            if c_eyeb_offset_full_pbone.custom_shape == None:
                c_eyeb_offset_full_pbone.custom_shape = bpy.data.objects.get("cs_torus_04_rot2")

            # color group
            c_eyeb_offset_full_pbone.bone_group = c_eyeb_full_pbone.bone_group

            # rot mode
            c_eyeb_offset_full_pbone.rotation_mode = "XYZ"

            # add a wrap property
            if not "wrap" in c_eyeb_full_pbone.keys():
                create_custom_prop(node=c_eyeb_full_pbone, prop_name='wrap', prop_val=1.0, prop_min=0.0, prop_max=1.0, prop_description="Wrap the eyebrows offset controllers on the mesh surface")

            for eyeb_main_name in eyebrows_list:
                eyeb_offset_name = eyeb_main_name.replace(lat_side, '_offset' + lat_side)
                eyeb_offset_pbone = get_pose_bone(eyeb_offset_name)
                eyeb_pbone = get_pose_bone(eyeb_main_name)
                # set rotation mode to euler
                eyeb_offset_pbone.rotation_mode = 'XYZ'
                # unlock transforms
                eyeb_offset_pbone.lock_location = eyeb_offset_pbone.lock_rotation = eyeb_offset_pbone.lock_scale = [
                    False, False, False]
                eyeb_pbone.lock_location = eyeb_pbone.lock_rotation = eyeb_pbone.lock_scale = [False, False, False]
                # custom shapes
                if eyeb_offset_pbone.custom_shape == None:
                    eyeb_offset_pbone.custom_shape = bpy.data.objects.get("cs_circle_01")
                eyeb_offset_pbone.bone_group = eyeb_pbone.bone_group
                # add constraints
                skw = eyeb_offset_pbone.constraints.get("Shrinkwrap")
                if skw == None:
                    skw = eyeb_offset_pbone.constraints.new("SHRINKWRAP")
                    skw.name = "Shrinkwrap"
                    skw.target = None  # we don't know the target yet, must be defined manually by the user
                    skw.distance = 0.0027
                    skw.shrinkwrap_type = 'TARGET_PROJECT'
                    skw.wrap_mode = 'ABOVE_SURFACE'
                    # add driver
                    dr = bpy.context.active_object.driver_add('pose.bones["' + eyeb_offset_name + '"].constraints["Shrinkwrap"].influence', -1)
                    dr.driver.expression = 'var'
                    if len(dr.driver.variables) == 0:
                        base_var = dr.driver.variables.new()
                    else:
                        base_var = dr.driver.variables[0]
                    base_var.type = 'SINGLE_PROP'
                    base_var.name = 'var'
                    base_var.targets[0].id = bpy.context.active_object
                    base_var.targets[0].data_path = 'pose.bones["' + c_eyeb_full_name + '"].["wrap"]'

    
    def unset_eyebrows_type3():
        #print("\n  Unset eyebrows type 3...")

        bpy.ops.object.mode_set(mode='EDIT')
        head_side = side[:-2]

        for lat_side in [head_side + ".l", head_side + ".r"]:

            bpy.ops.object.mode_set(mode='EDIT')
            
            c_eyeb_full_name = 'c_eyebrow_full' + lat_side

            # remove c_eyebrow_offset_full
            c_eyeb_offset_full_name = 'c_eyebrow_offset_full' + lat_side
            c_eyeb_offset_full = get_edit_bone(c_eyeb_offset_full_name)
            if c_eyeb_offset_full:
                delete_edit_bone(c_eyeb_offset_full)

            # delete offset for each main bone
            eyebrows_list = ["c_eyebrow_01_end" + lat_side, "c_eyebrow_01" + lat_side, "c_eyebrow_02" + lat_side,
                             "c_eyebrow_03" + lat_side]

            for eyeb_main_name in eyebrows_list:
                eyeb_offset_main = get_edit_bone(eyeb_main_name.replace(lat_side, '_offset' + lat_side))
                if eyeb_offset_main:
                    delete_edit_bone(eyeb_offset_main)

            bpy.ops.object.mode_set(mode='POSE')

            c_eyeb_full_pbone = get_pose_bone(c_eyeb_full_name)

            # delete wrap property
            if c_eyeb_full_pbone:
                if "wrap" in c_eyeb_full_pbone.keys():
                    del c_eyeb_full_pbone["wrap"]

    
    def unset_eyebrows_type2():
        #print("\n  Unset eyebrows type 2...")
        bpy.ops.object.mode_set(mode='EDIT')
        
        head_side = side[:-2]

        for lat_side in [head_side + ".l", head_side + ".r"]:
            # revert
            bpy.ops.object.mode_set(mode='EDIT')

            eyebrows_list = ["c_eyebrow_01_end" + lat_side, "c_eyebrow_01" + lat_side, "c_eyebrow_02" + lat_side,
                             "c_eyebrow_03" + lat_side]

            # parent controllers to c_eyebrow_full
            for eyeb_name in eyebrows_list:
                eyeb = get_edit_bone(eyeb_name)
                if eyeb == None:
                    break
                eyeb.parent = get_edit_bone("c_eyebrow_full" + lat_side)

            # remove eyebrow_01_end
            eyebrow_01_end_name = "eyebrow_01_end" + lat_side
            eyebrow_01_end = get_edit_bone(eyebrow_01_end_name)
            if eyebrow_01_end:
                delete_edit_bone(eyebrow_01_end)
                print("  deleted", eyebrow_01_end_name)

            c_eyeb_01_end = get_edit_bone(eyebrows_list[0])
            if c_eyeb_01_end:
                c_eyeb_01_end.use_deform = True

            # remove eyebrow_01_end_target
            eyebrow_01_end_target_name = "eyebrow_01_end_target" + head_side + lat_side
            eyebrow_01_end_target = get_edit_bone(eyebrow_01_end_target_name)
            if eyebrow_01_end_target:
                delete_edit_bone(eyebrow_01_end_target)
                print("  deleted", eyebrow_01_end_target_name)

            ### Unset Constraints
            bpy.ops.object.mode_set(mode='POSE')

            def remove_all_cns(pbone):
                if len(pbone.constraints):
                    for cn in pbone.constraints:
                        # print("  removed constraint", pbone.name, cn.name)
                        pbone.constraints.remove(cn)

            c_eyeb_01_end_pbone = get_pose_bone(eyebrows_list[0])
            if c_eyeb_01_end_pbone:
                remove_all_cns(c_eyeb_01_end_pbone)

            c_eyeb_01_pbone = get_pose_bone(eyebrows_list[1])
            if c_eyeb_01_pbone:
                remove_all_cns(c_eyeb_01_pbone)

            c_eyeb_02_pbone = get_pose_bone(eyebrows_list[2])
            if c_eyeb_02_pbone:
                remove_all_cns(c_eyeb_02_pbone)

            c_eyeb_03_pbone = get_pose_bone(eyebrows_list[3])
            if c_eyeb_03_pbone:
                remove_all_cns(c_eyeb_03_pbone)


    def set_lips_offset():# experimental
        head_side = side[:-2]
        
        upper_lips = ['c_lips_top_offset'+side, 'c_lips_top_offset'+head_side+'.l', 'c_lips_smile_offset'+head_side+'.l']
      
        # add .r side
        for i in upper_lips:
            if i.endswith('.l'):
                upper_lips.append(i[:-2]+'.r') 

        upper_lips += ard.get_variable_lips(side, type='OFFSET', levels=['top_'])
     
        down_lips = ['c_lips_bot_offset'+side, 'c_lips_bot_offset'+head_side+'.l']
       
        #   add .r side
        for i in down_lips:
            if i.endswith('.l'):
                down_lips.append(i[:-2]+'.r')       
        
        down_lips += ard.get_variable_lips(side, type='OFFSET', levels=['bot_'])

        if lips_offset:
            print("\n  Setting lips offset controller...")
            bpy.ops.object.mode_set(mode='EDIT')
          
            ## Create bones
            # create the c_lips_offset bone
            c_lips_offset_name = "c_lips_main_offset" + side
            c_lips_offset = get_edit_bone(c_lips_offset_name)
            if c_lips_offset == None:
                c_lips_offset = create_edit_bone(c_lips_offset_name)                
                lips_top = get_edit_bone("c_lips_top" + side)
                lips_bot = get_edit_bone("c_lips_bot" + side)
                c_lips_offset.head = (lips_top.head + lips_bot.head) * 0.5
                c_lips_offset.tail = c_lips_offset.head + (lips_top.tail - lips_top.head) * 10
                head_bone = get_edit_bone("head" + side)
                c_lips_offset.roll = head_bone.roll
            c_lips_offset.use_deform = False
            set_bone_layer(c_lips_offset, 1)

            # parent it to c_skull_01.x
            if skull_bones:
                c_lips_offset.parent = get_edit_bone("c_skull_01"+side)
            else:
                c_lips_offset.parent = get_edit_bone("head"+side)

            # parent upper offset bones to the c_lips_offset
            
            for lip in upper_lips:
                lip_bone = get_edit_bone(lip)
                lip_bone.parent = c_lips_offset

            # create the lips_offset_down bone
            lips_offset_dwn_name = "lips_main_offset_down" + side
            lips_offset_dwn = get_edit_bone(lips_offset_dwn_name)
            if lips_offset_dwn == None:
                lips_offset_dwn = create_edit_bone(lips_offset_dwn_name)
                print("  created", lips_offset_dwn_name)

            copy_bone_transforms(c_lips_offset, lips_offset_dwn)
            set_bone_layer(lips_offset_dwn, 8)
            lips_offset_dwn.use_deform = False
            # parent it to jawbone.x
            lips_offset_dwn.parent = get_edit_bone("jawbone" + side)

            # create the jaw_base bone
            jaw_base_name = "jaw_base" + side
            jaw_base = get_edit_bone(jaw_base_name)
            if jaw_base == None:
                jaw_base = create_edit_bone(jaw_base_name)
                print("  created", jaw_base_name)

            copy_bone_transforms(get_edit_bone("jawbone" + side), jaw_base)

            if skull_bones:
                jaw_base.parent = get_edit_bone("c_skull_01" + side)
            else:
                jaw_base.parent = get_edit_bone("head" + side)

            jaw_base.use_deform = False
            set_bone_layer(jaw_base, 8)

            # parent the down offset bones to lips_offset_down
            for lip in down_lips:
                lip_bone = get_edit_bone(lip)
                lip_bone.parent = lips_offset_dwn

            # parent jaw_ret_bone to lips_offset_down
            jaw_ret_name = "jaw_ret_bone" + side
            jaw_ret = get_edit_bone(jaw_ret_name)
            jaw_ret.parent = lips_offset_dwn

            ## Set constraints
            bpy.ops.object.mode_set(mode='POSE')

            # set the jaw_ret_bone constraint
            # set the original Copy Transforms constraint to 0
            jaw_ret_pbone = get_pose_bone(jaw_ret_name)
            jaw_ret_pbone.constraints[0].influence = 0

            # add a new Copy Rot constraint to jaw_base.x, influence 0.5
            cns_rot = jaw_ret_pbone.constraints.get("Copy Rotation")
            if cns_rot == None:
                cns_rot = jaw_ret_pbone.constraints.new("COPY_ROTATION")
                print("  created constraint", jaw_ret_pbone.name, "Copy Rotation")
            cns_rot.name = "Copy Rotation"
            cns_rot.target = rig
            cns_rot.subtarget = "jaw_base" + side
            cns_rot.influence = 0.5

            # set the lips_offset_down constraints
            # add Copy Loc to c_lips_offset.x, local space
            lips_off_dwn_pbone = get_pose_bone(lips_offset_dwn_name)
            cns_loc = lips_off_dwn_pbone.constraints.get("Copy Location")
            if cns_loc == None:
                cns_loc = lips_off_dwn_pbone.constraints.new("COPY_LOCATION")
                print("  created constraint", lips_off_dwn_pbone.name, "Copy Location")
            cns_loc.name = "Copy Location"
            cns_loc.target = rig
            cns_loc.subtarget = c_lips_offset_name
            cns_loc.target_space = cns_loc.owner_space = "LOCAL"

            # add Copy Rot to c_lips_offset.x, local space
            cns_rot = lips_off_dwn_pbone.constraints.get("Copy Rotation")
            if cns_rot == None:
                cns_rot = lips_off_dwn_pbone.constraints.new("COPY_ROTATION")
                print("  created constraint", lips_off_dwn_pbone.name, "Copy Rotation")
            cns_rot.name = "Copy Rotation"
            cns_rot.target = rig
            cns_rot.subtarget = c_lips_offset_name
            cns_rot.target_space = cns_rot.owner_space = "LOCAL"

            # Set custom shapes
            c_lips_offset_pbone = get_pose_bone("c_lips_offset" + side)
            if c_lips_offset_pbone.custom_shape == None:
                cs_name = 'cs_c_lips_offset'
                if bpy.data.objects.get(cs_name) == None:
                    append_from_arp(nodes=[cs_name], type="object")
                    print("  appended custom shape", cs_name)
                c_lips_offset_pbone.custom_shape = get_object(cs_name)

            get_data_bone(c_lips_offset_pbone.name).show_wire = True

            # Set groups
            c_lips_offset_pbone.bone_group = bpy.context.active_object.pose.bone_groups.get("body.x")

            # Set rotation mode
            c_lips_offset_pbone.rotation_mode = 'XYZ'

            # set custom shape
            if c_lips_offset_pbone.custom_shape == None:
                c_lips_offset_pbone.custom_shape = get_object("cs_pos")


        else:  # disable lips offset
            print("\n  Remove lips offset controller...")
            bpy.ops.object.mode_set(mode='EDIT')

            
            ## Delete bones
            # c_lips_offset bone
            c_lips_offset_name = "c_lips_main_offset" + side
            c_lips_offset = get_edit_bone(c_lips_offset_name)
            if c_lips_offset:
                delete_edit_bone(c_lips_offset)
                #print("  Delete lips main offset")
                
            # parent upper offset bones to the c_skull_01
            c_skull_01 = get_edit_bone('c_skull_01'+side)
            head_def = get_edit_bone("head"+side)
            #print("  Parent lips...")
            for lip in upper_lips:                
                lip_bone = get_edit_bone(lip)
                
                if lip_bone == None:
                    continue
                    
                if skull_bones:                    
                    lip_bone.parent = c_skull_01
                else:
                    lip_bone.parent = head_def

            # delete lips_offset_down bone
            lips_offset_dwn_name = "lips_offset_down" + side
            lips_offset_dwn = get_edit_bone(lips_offset_dwn_name)
            if lips_offset_dwn:
                delete_edit_bone(lips_offset_dwn)
                #print("  Delete lips down offset")
                
            # delete the jaw_base bone
            jaw_base_name = "jaw_base" + side
            jaw_base = get_edit_bone(jaw_base_name)
            if jaw_base:
                delete_edit_bone(jaw_base)
                #print("  Delete jaw base")
                
                
            # parent the down offset bones to jawbone   
            jawbone_name = ard.mouth_bones_dict['jawbone']['name'][:-2]+side
            jawbone = get_edit_bone(jawbone_name)
            #print("  Parent lips down offset")
            for lip in down_lips:
                lip_bone = get_edit_bone(lip)
                if lip_bone == None:
                    continue
                lip_bone.parent = jawbone

            # parent jaw_ret and c_jaw to to c_skull_01
            jaw_ret_name = ard.mouth_bones_dict['jaw_ret_bone']['name'][:-2] + side
            c_jaw_name = ard.mouth_bones_dict['c_jawbone']['name'][:-2] + side
            #print("  Parent jaw_ret...")
            for jaw_name in [jaw_ret_name, c_jaw_name, jawbone_name]:
                jaw = get_edit_bone(jaw_name)
                if jaw:
                    if skull_bones:            
                        jaw.parent = c_skull_01
                    else:
                        jaw.parent = head_def

            ## Set constraints
            #print("  Set constraints...")            
            
            bpy.ops.object.mode_set(mode='POSE')
            #print("  Switched Pose mode")
            # set the jaw_ret_bone constraint
            # set the original Copy Transforms constraint to 0.5
            jaw_ret_pbone = get_pose_bone(jaw_ret_name)
            if jaw_ret_pbone:
                jaw_ret_pbone.constraints[0].influence = 0.5

                # remove the new Copy Rot constraint to jaw_base.x
                cns_rot = jaw_ret_pbone.constraints.get("Copy Rotation")
                if cns_rot:
                    jaw_ret_pbone.constraints.remove(cns_rot)
                    
            print('  Done')
            
            
    def set_lips_corner_offset():# experimental
        if lips_corner_offset:
            print("\n  Setting lips corner offset...")
            bpy.ops.object.mode_set(mode='EDIT')

            head_side = side[:-2]
            head_def = get_edit_bone('head'+side)
            jawbone = get_edit_bone('jawbone'+side)
            c_lips_offset = get_edit_bone('c_lips_main_offset'+side)
            c_skull_01 = get_edit_bone('c_skull_01'+side)

            ### Create bones

            # lips_corner_middle.x
            lips_cor_mid_name = 'lips_corner_middle'+side
            lips_cor_mid = get_edit_bone(lips_cor_mid_name)
            if lips_cor_mid == None:
                lips_cor_mid = create_edit_bone(lips_cor_mid_name)                
                # same transforms as jawbone.x, scaled
                copy_bone_transforms(jawbone, lips_cor_mid)
                lips_cor_mid.tail += (lips_cor_mid.head - lips_cor_mid.tail) * 0.2
            
            lips_cor_mid.use_deform = False           
            set_bone_layer(lips_cor_mid, 8)
            
            # parent to c_lips_offset.x or c_skull_01
            if skull_bones:
                lips_cor_mid.parent = c_lips_offset if c_lips_offset else c_skull_01
            else:
                lips_cor_mid.parent = c_lips_offset if c_lips_offset else head_def

            # lips_retain_corner.l/.r bones
            for lat_side in [".l", ".r"]:
                lips_ret_corn_name = "lips_retain_corner" + head_side + lat_side
                lips_ret_corn = get_edit_bone(lips_ret_corn_name)
                if lips_ret_corn == None:
                    lips_ret_corn = create_edit_bone(lips_ret_corn_name)
                 
                    # same transforms as jawbone.x, scaled
                    copy_bone_transforms(jawbone, lips_ret_corn)
                    lips_ret_corn.tail += (lips_ret_corn.head - lips_ret_corn.tail) * 0.4
                # deform (must deform the lips corners)
                lips_ret_corn.use_deform = True
                # layer 8
                set_bone_layer(lips_ret_corn, 8)
                # parent to c_lips_offset if any or c_skull_01
                if skull_bones:
                    lips_ret_corn.parent = c_lips_offset if c_lips_offset else c_skull_01
                else:
                    lips_ret_corn.parent = c_lips_offset if c_lips_offset else head_def

            # c_lips_corner_depth .l/.r bones
            for lat_side in [".l", ".r"]:
                lips_smile_ref = get_edit_bone("lips_smile_ref" + head_side + lat_side)
                c_lips_corn_d_name = "c_lips_corner_depth" + head_side + lat_side
                c_lips_corn_d = get_edit_bone(c_lips_corn_d_name)
                if c_lips_corn_d == None:
                    c_lips_corn_d = create_edit_bone(c_lips_corn_d_name)                    
                    # transforms
                    # head = lips_smile_ref.l/.r head
                    c_lips_corn_d.head = lips_smile_ref.head.copy()
                    fac = 1 if lat_side == ".l" else -1
                    c_lips_corn_d.tail = c_lips_corn_d.head + (lips_smile_ref.x_axis.normalized() * lips_smile_ref.length) * fac
                    # roll = copy from jawbone_ref.x + 90° (X up)
                    align_bone_x_axis(c_lips_corn_d, jawbone.x_axis)
                    c_lips_corn_d.roll += radians(-90) if lat_side == ".r" else radians(90)

                # no deform
                c_lips_corn_d.use_deform = False
                # layer 0
                set_bone_layer(c_lips_corn_d, 0)
                # parent to lips_corner_middle.x
                c_lips_corn_d.parent = lips_cor_mid

            bpy.ops.object.mode_set(mode='POSE')

            ### Add constraints
            # lips_retain_corner.l/.r:
            for lat_side in [".l", ".r"]:
                lips_ret_corn_name = "lips_retain_corner" + head_side + lat_side
                lips_ret_corn_pbone = get_pose_bone(lips_ret_corn_name)
                # Copy Rotation constraint
                rot_cns = lips_ret_corn_pbone.constraints.get("Copy Rotation")
                if rot_cns == None:
                    rot_cns = lips_ret_corn_pbone.constraints.new("COPY_ROTATION")
               
                    rot_cns.name = "Copy Rotation"
                    rot_cns.target = bpy.context.active_object
                    # subtarget jawbone.x
                    rot_cns.subtarget = "jawbone" + side
                    # driven influence:
                    # expression = 0.5 * (1 - var*80)
                    # var = pose.bones["c_lips_smile.l/.r"].location[1]
                    dr_data_path = 'pose.bones["' + lips_ret_corn_name + '"].constraints["Copy Rotation"].influence'
                    dr = bpy.context.active_object.animation_data.drivers.find(dr_data_path)
                    if dr == None:
                        dr = bpy.context.active_object.driver_add('pose.bones["' + lips_ret_corn_name + '"].constraints["Copy Rotation"].influence', -1)
                    
                        dr.driver.expression = '0.5 * (1 - var*80)'
                    if len(dr.driver.variables) == 0:                     
                        base_var = dr.driver.variables.new()
                    else:
                        base_var = dr.driver.variables[0]
                    base_var.type = 'SINGLE_PROP'
                    base_var.name = 'var'
                    base_var.targets[0].id = bpy.context.active_object
                    base_var.targets[0].data_path = 'pose.bones["c_lips_smile' + head_side + lat_side + '"].location[1]'

                # Copy Location constraint
                loc_cns = lips_ret_corn_pbone.constraints.get("Copy Location")
                if loc_cns == None:
                    loc_cns = lips_ret_corn_pbone.constraints.new("COPY_LOCATION")                
                    loc_cns.name = "Copy Location"
                    loc_cns.target = bpy.context.active_object
                    # subtarget c_lips_corner_depth.l/.r
                    loc_cns.subtarget = "c_lips_corner_depth" + head_side + lat_side
                    # Y axis only
                    loc_cns.use_x = loc_cns.use_z = False
                    loc_cns.use_y = True
                    # Local Space
                    loc_cns.target_space = loc_cns.owner_space = "LOCAL"

            # lips_corner_middle.x
            lips_corn_mid_pbone = get_pose_bone(lips_cor_mid_name)
            # Copy Rotation constraint
            cns_rot = lips_corn_mid_pbone.constraints.get("Copy Rotation")
            if cns_rot == None:
                cns_rot = lips_corn_mid_pbone.constraints.new("COPY_ROTATION")             
                cns_rot.name = "Copy Rotation"
                cns_rot.target = bpy.context.active_object
                # subtarget jawbone.x
                cns_rot.subtarget = "jawbone" + side
                # influence 0.5
                cns_rot.influence = 0.5

            ### Set Rotation Modes - Locks transforms
            for lat_side in [".l", ".r"]:
                c_lips_corn_d_name = "c_lips_corner_depth" + head_side + lat_side
                c_lips_corn_d = get_pose_bone(c_lips_corn_d_name)
                c_lips_corn_d.lock_location[0] = c_lips_corn_d.lock_location[2] = True
                c_lips_corn_d.rotation_mode = 'XYZ'
                c_lips_corn_d.lock_rotation[0] = c_lips_corn_d.lock_rotation[1] = c_lips_corn_d.lock_rotation[2] = True
                c_lips_corn_d.lock_scale[0] = c_lips_corn_d.lock_scale[1] = c_lips_corn_d.lock_scale[2] = True

                # Set custom shapes
                if c_lips_corn_d.custom_shape == None:
                    cs_name = 'cs_arrow_x_up' if lat_side == ".l" else "cs_arrow_x_down"
                    if bpy.data.objects.get(cs_name) == None:
                        append_from_arp(nodes=[cs_name], type="object")
                      
                    c_lips_corn_d.custom_shape = bpy.data.objects.get(cs_name)

                c_lips_corn_d.custom_shape_transform = get_pose_bone("c_lips_smile" + head_side + lat_side)
                get_data_bone(c_lips_corn_d.name).show_wire = True

                # Set groups
                c_lips_corn_d.bone_group = bpy.context.active_object.pose.bone_groups.get("body.x")


        else:  # disable lips corner offset
            print("\nRemove lips corner offset...")
            bpy.ops.object.mode_set(mode='EDIT')

            head_side = side[:-2]

            ### Delete bones

            # lips_corner_middle.x
            lips_cor_mid_name = "lips_corner_middle" + side
            lips_cor_mid = get_edit_bone(lips_cor_mid_name)
            if lips_cor_mid:
                delete_edit_bone(lips_cor_mid)         

            # lips_retain_corner.l/.r bones
            for lat_side in [".l", ".r"]:
                lips_ret_corn_name = "lips_retain_corner" + head_side + lat_side
                lips_ret_corn = get_edit_bone(lips_ret_corn_name)
                if lips_ret_corn:
                    delete_edit_bone(lips_ret_corn)                 

            # c_lips_corner_depth .l/.r bones
            for lat_side in [".l", ".r"]:
                lips_smile_ref = get_edit_bone("lips_smile_ref" + head_side + lat_side)
                c_lips_corn_d_name = "c_lips_corner_depth" + head_side + lat_side
                c_lips_corn_d = get_edit_bone(c_lips_corn_d_name)
                if c_lips_corn_d:
                    delete_edit_bone(c_lips_corn_d)                

            bpy.ops.object.mode_set(mode='POSE')

    
    def set_lips_big_masters():# experimental
        master_top_name = "lips_top_big_master_ref" + side
        master_bot_name = "lips_bot_big_master_ref" + side
        c_master_top_name = "c_lips_top_big_master" + side
        c_master_bot_name = "c_lips_bot_big_master" + side

        if lips_big_masters:
            print("\nSetting lips masters...")
            bpy.ops.object.mode_set(mode='EDIT')

            head_side = side[:-2]

            # create ref bones
            master_top_ref = get_edit_bone(master_top_name)
            master_bot_ref = get_edit_bone(master_bot_name)
            lips_top_ref = get_edit_bone("lips_top_ref" + side)
            lips_bot_ref = get_edit_bone("lips_bot_ref" + side)

            if master_top_ref == None:
                master_top_ref = create_edit_bone(master_top_name)
                copy_bone_transforms(lips_top_ref, master_top_ref)
                master_top_ref.tail = master_top_ref.head + (master_top_ref.tail - master_top_ref.head) * 1.5
                set_bone_layer(master_top_ref, 17)
                master_top_ref.use_deform = False

            if master_bot_ref == None:
                master_bot_ref = create_edit_bone(master_bot_name)
                copy_bone_transforms(lips_bot_ref, master_bot_ref)
                master_bot_ref.tail = master_bot_ref.head + (master_bot_ref.tail - master_bot_ref.head) * 1.5
                set_bone_layer(master_bot_ref, 17)
                master_bot_ref.use_deform = False

            # create control bones
            c_master_top = get_edit_bone(c_master_top_name)
            c_master_bot = get_edit_bone(c_master_bot_name)

            if c_master_top == None:
                c_master_top = create_edit_bone(c_master_top_name)
                copy_bone_transforms(master_top_ref, c_master_top)
                set_bone_layer(c_master_top, 1)
                c_master_top.use_deform = False
                c_master_top.parent = get_edit_bone("c_lips_top_offset" + side).parent

            if c_master_bot == None:
                c_master_bot = bpy.context.active_object.data.edit_bones.new(c_master_bot_name)
                copy_bone_transforms(master_bot_ref, c_master_bot)
                set_bone_layer(c_master_bot, 1)
                c_master_bot.use_deform = False
                c_master_bot.parent = get_edit_bone("c_lips_bot_offset" + side).parent

            # set the lips_offset parent
            top_lips_names = ['c_lips_top_offset'+side, 'c_lips_top_offset'+head_side,
                              'c_lips_top_01_offset'+head_side]

            for n in top_lips_names:
                if n.endswith(".x"):                 
                    get_edit_bone(n).parent = c_master_top
                else:
                    for _s in [".l", ".r"]:
                        get_edit_bone(n + _s).parent = c_master_top

            bot_lips_names = ['c_lips_bot_offset'+side, 'c_lips_bot_offset'+head_side,
                              'c_lips_bot_01_offset'+head_side]

            for n in bot_lips_names:
                if n.endswith(".x"):
                    get_edit_bone(n).parent = c_master_bot
                else:
                    for _s in [".l", ".r"]:
                        get_edit_bone(n + _s).parent = c_master_bot

            bpy.ops.object.mode_set(mode='POSE')

            c_master_top_pbone = get_pose_bone(c_master_top_name)
            c_master_bot_pbone = get_pose_bone(c_master_bot_name)
            c_lips_top_pbone = get_pose_bone('c_lips_top'+side)

            # set rotation mode
            c_master_top_pbone.rotation_mode = c_master_bot_pbone.rotation_mode = "XYZ"

            # set custom shapes
            if c_master_top_pbone.custom_shape == None:
                c_master_top_pbone.custom_shape = get_object("cs_circle_02")
            if c_master_bot_pbone.custom_shape == None:
                c_master_bot_pbone.custom_shape = get_object("cs_circle_02")

            # set groups
            c_master_top_pbone.bone_group = c_master_bot_pbone.bone_group = c_lips_top_pbone.bone_group

            print("  lips masters set.")

        else:  # disable
            print("\nUnset lips masters...")
            bpy.ops.object.mode_set(mode='EDIT')

            # delete ref bones
            master_top_ref = get_edit_bone(master_top_name)
            master_bot_ref = get_edit_bone(master_bot_name)

            if master_top_ref:                
                delete_edit_bone(master_top_ref)
            if master_bot_ref:              
                delete_edit_bone(master_bot_ref)

            # delete control bones
            c_master_top = get_edit_bone(c_master_top_name)
            c_master_bot = get_edit_bone(c_master_bot_name)

            if c_master_top:             
                delete_edit_bone(c_master_top)
            if c_master_bot:             
                delete_edit_bone(c_master_bot)
            
            print("  lips master unset.")

    
    def set_skull_bones():
        # Set skull bones
        if skull_bones:
            head = get_edit_bone("head"+side)
            #print("  Add skull bones")
            for skull_name in ard.skulls:
                skull_name = skull_name[:-2] + side
                skull_ebone = get_edit_bone(skull_name)
                
                # add skull bone
                if skull_ebone == None:
                    skull_ebone = create_edit_bone(skull_name, deform=True)                    
                    copy_bone_transforms(head_ref, skull_ebone)                  
                    set_bone_layer(skull_ebone, 1)                 
                    skull_ebone.parent = head
                    

            bpy.ops.object.mode_set(mode='POSE')
            

            for skull_name in ard.skulls:
                skull_name_def = skull_name[:-2] + side
                skull_pbone = get_pose_bone(skull_name_def)
                # custom shape
                if skull_pbone.custom_shape == None:
                    cs_name = 'cs_user_'+skull_name

                    if get_object(cs_name) == None:
                        append_from_arp(nodes=[cs_name], type="object")

                    cs = get_object('cs_user_'+skull_name)
                    skull_pbone.custom_shape = cs

                # group
                if skull_pbone.bone_group == None:
                    bgroup = get_pose_bone('head.x').bone_group
                    skull_pbone.bone_group = bgroup

                # rotation mode
                skull_pbone.rotation_mode = "XYZ"

            bpy.ops.object.mode_set(mode='EDIT')


        else:
            print("  Remove skull bones")
            for skull_name in ard.skulls:
                skull_name = skull_name[:-2] + side
                skull_ebone = get_edit_bone(skull_name)
                if skull_ebone:
                    delete_edit_bone(skull_ebone)

    
    def set_eyelids_tweak():
        eye_sides = []
        if eye_l_enabled:
            eye_sides.append('.l')
        if eye_r_enabled:
            eye_sides.append('.r')
    
        head_side = side[:-2]
        
        for eye_side in eye_sides:
            print("  Set eyelids tweak", eye_side)  
            
            bpy.ops.object.mode_set(mode='EDIT')
            
            created_controllers = []
            
            for lvl in ['top', 'bot']:                
                eyel_ref_main_name = 'eyelid_'+lvl+'_ref'+head_side+eye_side
                eyel_ref_main = get_edit_bone(eyel_ref_main_name)                
            
                # ref
                eyelid_twk_ref_name = 'eyelid_twk_'+lvl+'_ref'+head_side+eye_side
                eyelid_twk_ref = get_edit_bone(eyelid_twk_ref_name)
                c_eyelid_twk_name = 'c_eyelid_twk_'+lvl+head_side+eye_side
                c_eyelid_twk = get_edit_bone(c_eyelid_twk_name)
                
                if enable_eyelids_tweak:# enable
                    # ref
                    if eyelid_twk_ref == None:
                        eyelid_twk_ref = create_edit_bone(eyelid_twk_ref_name)
                        fac = 1 if lvl =='top' else -1 
                        eyelid_twk_ref.head = eyel_ref_main.tail + (eyel_ref_main.tail-eyel_ref_main.head).magnitude * eyel_ref_main.z_axis.normalized()*fac * 0.3
                        eyelid_twk_ref.tail = eyelid_twk_ref.head + (eyel_ref_main.tail-eyel_ref_main.head).magnitude * eyel_ref_main.y_axis.normalized() * 0.3                                      
                        align_bone_z_axis(eyelid_twk_ref, eyel_ref_main.z_axis)
                    # set layer
                    set_bone_layer(eyelid_twk_ref, 17)
                    
                    # cont
                    if c_eyelid_twk == None:
                        c_eyelid_twk = create_edit_bone(c_eyelid_twk_name, deform=True)
                        copy_bone_transforms(eyelid_twk_ref, c_eyelid_twk)
                        # set layer
                        set_bone_layer(c_eyelid_twk, 0)
                        # set parent
                        c_eye_offset_name = ard.eye_bones_dict['eye_offset']['name']+head_side+eye_side
                        c_eyelid_twk.parent = get_edit_bone(c_eye_offset_name)
                        created_controllers.append(c_eyelid_twk_name)
                    
                else:# disable
                    if eyelid_twk_ref:
                        delete_edit_bone(eyelid_twk_ref)
                    if c_eyelid_twk:
                        delete_edit_bone(c_eyelid_twk)
                        
                        
            # pose settings
            bpy.ops.object.mode_set(mode='POSE')
            
            for cname in created_controllers:
                pb = get_pose_bone(cname)
                # rot mode
                pb.rotation_mode = 'XYZ'
                # color group
                set_bone_color_group(rig, pb, 'body'+eye_side)
                # shape
                if pb.custom_shape == None:
                    set_bone_custom_shape(pb, 'cs_sphere')  
                        
            bpy.ops.object.mode_set(mode='EDIT')
                
        
    def set_eyelids_amount():
        eye_sides = []
        if eye_l_enabled:
            eye_sides.append('.l')
        if eye_r_enabled:
            eye_sides.append('.r')
        
        head_side = side[:-2]
        
        for eye_side in eye_sides:
            print("  Set eyelids amount", eye_side)
            
            bpy.ops.object.mode_set(mode='EDIT')            
            
            corner_01_ref_name = 'eyelid_corner_01_ref'+head_side+eye_side
            corner_02_ref_name = 'eyelid_corner_02_ref'+head_side+eye_side
            corner_01_ref = get_edit_bone(corner_01_ref_name)
            corner_02_ref = get_edit_bone(corner_02_ref_name)
         
            created_ref_bones = []
            removed_ref_bones = []
            
            # setup ref bones
            mid_idx = int(eyelids_amount/2)
            
            for lvl in ['top', 'bot']:
                
                eyel_ref_main_name = 'eyelid_'+lvl+'_ref'+head_side+eye_side
                eyel_ref_main = get_edit_bone(eyel_ref_main_name)
            
                eyel_vec1 = eyel_ref_main.tail - corner_01_ref.tail
                eyel_vec2 = corner_02_ref.tail - eyel_ref_main.tail
                vec1_div = eyel_vec1 / ((eyelids_amount+1)/2)
                regul = 0 if eyelids_amount % 2 == 0 else 1
                vec2_div = eyel_vec2 / (((eyelids_amount+2+regul)/2))
                
                for i in range(1, 32+1):
                    idx = '%02d' % i
                    eyel_ref_name = 'eyelid_'+lvl+'_'+idx+'_ref'+head_side+eye_side
                    eyel_ref = get_edit_bone(eyel_ref_name)
                    
                    mode = 'add' if i <= eyelids_amount else 'remove'                    
                    
                    set_transforms = False
                    
                    if mode == 'add':
                        created_ref_bones.append(eyel_ref_name)
                        
                        if eyel_ref == None:
                            eyel_ref = create_edit_bone(eyel_ref_name)
                            set_transforms = True
                        # set transforms
                        if set_transforms or force_eyelids_updt_transf or eyelids_updt_transf:                            
                            eyel_ref.head = eyel_ref_main.head.copy()
                            
                            if eyelids_amount == 3:
                                if i == 1:
                                    eyel_ref.tail = corner_01_ref.tail + (eyel_ref_main.tail-corner_01_ref.tail)*0.5
                                elif i == 2:
                                    eyel_ref.tail = eyel_ref_main.tail.copy() + (corner_02_ref.tail-eyel_ref_main.tail)*0.1
                                elif i == 3:
                                    eyel_ref.tail = eyel_ref_main.tail + (corner_02_ref.tail-eyel_ref_main.tail)*0.5
                            elif eyelids_amount == 2:
                                if i == 1:
                                    eyel_ref.tail = corner_01_ref.tail + (eyel_ref_main.tail-corner_01_ref.tail)*0.5
                                elif i == 2:
                                    eyel_ref.tail = eyel_ref_main.tail + (corner_02_ref.tail-eyel_ref_main.tail)*0.5
                            elif eyelids_amount == 1:
                                if i == 1:
                                    eyel_ref.tail = eyel_ref_main.tail.copy() + (corner_02_ref.tail-eyel_ref_main.tail)*0.1
                            
                            else:# align in a circular path around the eyelids
                                vec_div = vec1_div if i <= mid_idx else vec2_div
                                origin = corner_01_ref.tail.copy() if i <= mid_idx else eyel_ref_main.tail.copy()
                                inc = i if i <= mid_idx else (i - mid_idx)
                                p_linear = origin + (vec_div*inc) 
                                p_proj = project_point_onto_plane(p_linear, eyel_ref_main.tail, eyel_ref_main.y_axis)# y
                                p_proj = project_point_onto_plane(p_proj, eyel_ref_main.tail, eyel_ref_main.z_axis)                           
                                fac = 1-(inc/(eyelids_amount/2)) if i <= mid_idx else inc/(eyelids_amount/2)
                                if fac > 1:
                                    fac = 1
                                
                                eyel_ref.tail = (p_linear * fac) + (p_proj * (1-fac))
                            
                            #eyel_ref.tail = p_linear
                            align_bone_z_axis(eyel_ref, eyel_ref_main.z_axis)
                            
                        # set layer
                        set_bone_layer(eyel_ref, 17)
                    
                    elif mode == 'remove':# remove out of range
                        if eyel_ref:
                            delete_edit_bone(eyel_ref)
                        removed_ref_bones.append(eyel_ref_name)
                        
                    
                
            
            # setup controllers
            for ref_bname in created_ref_bones:
                eye_side = get_bone_side(ref_bname)[-2:]
                eyel_ref = get_edit_bone(ref_bname)
                cname = 'c_'+ref_bname.replace('_ref','')
                c_eyel_eb = get_edit_bone(cname)
                if c_eyel_eb == None:
                    c_eyel_eb = create_edit_bone(cname, deform=True)
                    set_bone_layer(c_eyel_eb, 0)
                    
                copy_bone_transforms(eyel_ref, c_eyel_eb)
                
                if 'top' in cname:
                    c_eyel_eb.parent = get_edit_bone('eyelid_top'+head_side+eye_side)
                elif 'bot' in cname:
                    c_eyel_eb.parent = get_edit_bone('eyelid_bot'+head_side+eye_side)
               
            #   remove out of range
            for ref_bname in removed_ref_bones:
                cname = 'c_'+ref_bname.replace('_ref','')
                c_eyel_eb = get_edit_bone(cname)
                if c_eyel_eb:
                    delete_edit_bone(c_eyel_eb)
                    
            
                    
            # set pose settings
            bpy.ops.object.mode_set(mode='POSE')     
            
            for ref_bname in created_ref_bones: 
                cname = 'c_'+ref_bname.replace('_ref','')
                c_eyel_pb = get_pose_bone(cname)
                eye_side = get_bone_side(ref_bname)[-2:]
                # rot mode
                c_eyel_pb.rotation_mode = 'XYZ'
                # color group
                set_bone_color_group(rig, c_eyel_pb, 'body'+eye_side)
                # shape
                if c_eyel_pb.custom_shape == None:
                    set_bone_custom_shape(c_eyel_pb, 'cs_c_eyelid_micro')
                
                
            bpy.ops.object.mode_set(mode='EDIT')   
            
            
    
    set_skull_bones()

    # Set facial
    if enable:
        #create_facial()
        set_facial_sublimb(module_name='rig_mouth', active=mouth_enabled, bones_list=ard.mouth_bones+ard.mouth_ref, matrix_ref_bone=ard.facial_ref_dict['jaw'])
        set_lips_amount()          
        set_autolips()
        set_lips_masters()
        
        set_facial_sublimb(module_name='rig_teeth', active=teeth_enabled and mouth_enabled, bones_list=ard.teeth_bones+ard.teeth_ref, matrix_ref_bone=ard.teeth_bones[0])
        set_facial_sublimb(module_name='rig_tongue', active=tongue_enabled and mouth_enabled, bones_list=ard.tongue_bones+ard.tongue_ref, matrix_ref_bone=ard.tongue_bones[0])
        set_facial_sublimb(module_name='rig_chins', active=chins_enabled, bones_list=ard.chin_bones+ard.chin_ref, matrix_ref_bone=ard.chin_bones[0])        
        set_facial_sublimb(module_name='rig_noses', active=noses_enabled, bones_list=ard.nose_bones+ard.nose_ref, matrix_ref_bone=ard.nose_bones[0])
        
        set_facial_sublimb(module_name='rig_eye_l', active=eye_l_enabled, bones_list=ard.eye_bones_left, matrix_ref_bone=ard.eye_bones_left[0])
        set_facial_sublimb(module_name='rig_eye_r', active=eye_r_enabled, bones_list=ard.eye_bones_right, matrix_ref_bone=ard.eye_bones_right[0])
        if eye_l_enabled == False and eye_r_enabled == False:
            delete_eye_target_mid(side)
        
        set_eyelids_amount()
        
        set_eyelids_tweak()
        set_facial_sublimb(module_name='rig_eyebrow_l', active=eyebrow_l_enabled, bones_list=ard.eyebrow_bones_left, matrix_ref_bone=ard.eyebrow_bones_left[0])
        set_facial_sublimb(module_name='rig_eyebrow_r', active=eyebrow_r_enabled, bones_list=ard.eyebrow_bones_right, matrix_ref_bone=ard.eyebrow_bones_right[0])
        set_facial_sublimb(module_name='rig_cheeks', active=cheeks_enabled, bones_list=ard.cheek_bones+ard.cheek_ref, matrix_ref_bone=ard.cheek_bones[0])
        
        
        ### experimental
        set_lips_offset()
        set_lips_corner_offset()
        set_lips_big_masters()        
        
        unset_eyebrows_type2()
        unset_eyebrows_type3()
        if eyebrows_type == 'type_2':
            set_eyebrows_type2()
        elif eyebrows_type == 'type_3':
            set_eyebrows_type3()
        ###
        
    else:
        delete_facial()

    bpy.ops.object.mode_set(mode='EDIT')

    # Restore layers
    restore_armature_layers(layers_select)   
    bpy.context.active_object.data.use_mirror_x = xmirror_state

    # restore picker
    restore_proxy_picker(proxy_picker_state)

    print("Facial set.")

    # end set_facial()


def scale_from_origin(ed_bone=None, head_coords=None, tail_coords=None, center=None, factor=None):
    if head_coords == None and tail_coords == None:
        head_coords = ed_bone.head
        tail_coords = ed_bone.tail

    ed_bone.head = center + ((head_coords - center) * factor)
    ed_bone.tail = center + ((tail_coords - center) * factor)


def set_ears(ears_amount, side_arg=None, offset_arg=None):
    current_mode = bpy.context.mode
    bpy.ops.object.mode_set(mode='EDIT')

    # save X-Mirror state
    xmirror_state = bpy.context.object.data.use_mirror_x
    bpy.context.object.data.use_mirror_x = False

    # Active all layers
    layers_select = enable_all_armature_layers()

    offset_translation = 0
    if offset_arg:
        offset_translation = offset_arg * 0.5

    sides = ['.l', '.r']

    # if the side is set, operate on the given ear sides only
    if side_arg:
        sides = [side_arg[:-2] + ".l", side_arg[:-2] + ".r"]

    # else, get selected ears side
    else:
        if len(get_selected_edit_bones()) > 0:
            b_name = get_selected_edit_bones()[0].name
            # only if it's a ref bone
            if len(b_name.split('_')) >= 3:
                if b_name.split('_')[2][:3] == 'ref' and b_name.split('_')[0] == 'ear':
                    sides = [b_name[-2:]]
                    if '_dupli' in b_name:
                        sides = [b_name[-12:]]
                        # sides = [dupli[:-2] + ".l", dupli[:-2] + ".r"]
                else:
                    print("No reference ear bone selected:", b_name)
        else:
            print("No bone selected")

    # print("ear sides", sides)

    # First delete all ears bones
    start_end_pos = [Vector((0, 0, 0)), Vector((0, 0, 0))]
    ear_parent_name = None

    for i in range(0, 16):
        for side in sides:
            ref_bone = get_edit_bone('ear_' + '%02d' % (i + 1) + '_ref' + side)
            if ref_bone:
                # save the start pos (first bone head position)
                if i == 0:
                    start_end_pos[0] = ref_bone.head.copy()

                    if ref_bone.parent:
                        ear_parent_name = ref_bone.parent.name

                # save the end pos (last bone head position)
                start_end_pos[1] = ref_bone.tail.copy()

                delete_edit_bone(ref_bone)

            control_bone = get_edit_bone('c_ear_' + '%02d' % (i + 1) + side)
            if control_bone:
                delete_edit_bone(control_bone)

                # proxy bones
            switch_bone_layer('c_ear_' + '%02d' % (i + 1) + '_proxy' + side, 0, 22, False)

    ear_vec = start_end_pos[1] - start_end_pos[0]
    ref_bones_list = []

    # If ears enabled, create bones
    if ears_amount > 0:
        ear_bones_list = []
        for i in range(0, ears_amount):

            for side in sides:
                # ref bones
                ref_bone_name = 'ear_' + '%02d' % (i + 1) + '_ref' + side
                ref_bone = get_edit_bone(ref_bone_name)
                ref_bones_list.append(ref_bone_name)
                if ref_bone == None:
                    ref_bone = bpy.context.active_object.data.edit_bones.new(ref_bone_name)
                    ear_bones_list.append(ref_bone.name)

                ref_bone.use_deform = False
                head_bone = get_edit_bone('head_ref.x')
                ref_bone['arp_duplicate'] = 1

                fac = 1
                if side[-2:] == ".r":
                    fac = -1

                # if a previous bone chain exists, match the new bones positions with this one
                if ear_vec.magnitude != 0.0:
                    ref_bone.head = start_end_pos[0] + (ear_vec * (i)) / ears_amount
                    ref_bone.tail = ref_bone.head + (ear_vec / ears_amount)


                # otherwise, use other default locations
                elif head_bone:
                    ref_bone.head = ((head_bone.tail + head_bone.head) * 0.5) + (
                            head_bone.x_axis.normalized() * head_bone.length * 0.2) * fac * (
                                            i + 2)
                    ref_bone.tail = ((head_bone.tail + head_bone.head) * 0.5) + (
                            head_bone.x_axis.normalized() * head_bone.length * 0.2) * fac * (
                                            i + 3)

                else:
                    ref_bone.head = Vector((0.1 * (i + 1) * fac, 0, 0))
                    ref_bone.tail = Vector((0.1 * (i + 2) * fac, 0, 0))

                ref_bone.head += Vector((offset_translation, 0, 0))
                ref_bone.tail += Vector((offset_translation, 0, 0))

                # parent
                if i == 0:
                    # parent by default the first bone to the head bone, if not already set
                    if not ear_parent_name:
                        if head_bone:
                            ref_bone.parent = head_bone
                            # print('1.parented ear bone to', head_bone.name)
                    else:
                        ref_bone.parent = get_edit_bone(ear_parent_name)
                        # print('2.parented ear bone to', ear_parent_name)
                else:
                    ref_bone.parent = get_edit_bone('ear_' + '%02d' % (i) + '_ref' + side)
                    # print('3.parented ear bone to', get_edit_bone('ear_' + '%02d' % (i)  + '_ref' + side))
                # control bones
                cont_bone_name = 'c_ear_' + '%02d' % (i + 1) + side
                cont_bone = get_edit_bone(cont_bone_name)

                if cont_bone == None:
                    cont_bone = bpy.context.active_object.data.edit_bones.new(cont_bone_name)
                    ear_bones_list.append(cont_bone.name)

                cont_bone.head, cont_bone.tail, cont_bone.roll = ref_bone.head, ref_bone.tail, ref_bone.roll

                # parent
                if get_edit_bone('c_ear_' + '%02d' % (i) + side):
                    cont_bone.parent = get_edit_bone('c_ear_' + '%02d' % (i) + side)

                # proxy bones
                switch_bone_layer('c_ear_' + '%02d' % (i + 1) + '_proxy' + side, 22, 0, False)

                # Set display parameters
                bpy.ops.object.mode_set(mode='POSE')

                for j in ear_bones_list:
                    pbone = get_pose_bone(j)

                    # bone group
                    grp_name = 'body' + j[-2:]
                    if bpy.context.active_object.pose.bone_groups.get(grp_name):
                        pbone.bone_group = bpy.context.active_object.pose.bone_groups[grp_name]

                    # custom shape
                    if not "_ref" in j:
                        cs_name = 'cs_torus_03'
                        if bpy.data.objects.get(cs_name) == None:
                            append_from_arp(nodes=[cs_name], type="object")

                        pbone.custom_shape = bpy.data.objects[cs_name]
                        set_custom_shape_scale(pbone, 1.0)
                        get_data_bone(pbone.name).show_wire = True

                        # Set layers
                    if not "_ref" in j:
                        # controller
                        get_data_bone(j).layers[0] = True
                        for idx, lay in enumerate(get_data_bone(j).layers):
                            if idx != 0 and idx != 31:
                                get_data_bone(j).layers[idx] = False
                            else:
                                get_data_bone(j).layers[idx] = True

                                # reference
                    else:
                        get_data_bone(j).layers[17] = True
                        for idx, lay in enumerate(get_data_bone(j).layers):
                            if idx != 17:
                                get_data_bone(j).layers[idx] = False

                bpy.ops.object.mode_set(mode='EDIT')

    # select ref bones
    bpy.ops.armature.select_all(action='DESELECT')
    for iname in ref_bones_list:
        select_edit_bone(iname)

    # restore X-Mirror state
    bpy.context.object.data.use_mirror_x = xmirror_state

    # Restore layers
    restore_armature_layers(layers_select)

    # restore saved mode
    restore_current_mode(current_mode)
    # end set_ears()


def is_object_arp(object):
    if object:
        if object.type == 'ARMATURE':
            if get_pose_bone('c_pos'):
                return True

    return False


def get_arp_type(rig):
    if len(rig.data.keys()) > 0:
        if "arp_rig_type" in rig.data.keys():
            return rig.data["arp_rig_type"]


def update_secondary_controllers(self, context):
    _edit_ref()


def update_show_ik_lines(self, context):
    if bpy.context.scene.arp_show_ik_chain_direction:
        bpy.ops.id.lines_fx(active=True)


# END FUNCTIONS


###########  UI PANEL  ###################
class ArpRigPanel:
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "ARP"


class ARP_PT_auto_rig_pro_panel(Panel, ArpRigPanel):
    bl_label = "Auto-Rig Pro"


    def draw(self, context):
        scn = context.scene
        object = context.active_object
        prop_type = None
        rig_is_selected = is_object_arp(object)


        if rig_is_selected:
            prop_type = get_arp_type(object)

            # check if required update is necessary
            if len(object.data.keys()):
                update_required = False

                if not "arp_updated" in object.data.keys():
                    update_required = True
                
                else:
                    up_id = int(object.data["arp_updated"].replace('.',''))
                    if up_id < 36118:
                        update_required = True

                if bpy.app.version >= (3,0,0):
                    if not 'arp_updated_3.0' in object.data.keys():
                        update_required = True

                if update_required:
                    col = self.layout.column()
                    col.scale_y = 1.2
                    op=col.operator("arp.update_armature", text="UPDATE REQUIRED!", icon="ERROR")
                    op.required = True
                    col.separator()
                    return

        layout = self.layout.column(align=True)
        row = layout.row()
        row.prop(scn, "arp_active_tab", expand=True)
        row.scale_y = 1.2
        layout.separator()
        
        # if locked (Quick Rig Preserve mode), do not show interface, not supposed to be editable
        locked_rig = False
        
        if object:
            if object.data:
                if "arp_locked" in object.data.keys():
                    locked_rig = True
              
        if locked_rig:
            col = self.layout.column()
            col.label(text='Locked rig', icon='LOCKED')
            col.label(text='Non editable rig')
                

        if scn.arp_active_tab != 'CREATE':
            return

        if not locked_rig:
            layout.separator()
            layout.separator()
            col = layout.column(align=False)
            row = col.row(align=True)
            row.operator_menu_enum("arp.append_arp", 'rig_presets', text="Add Armature")
            row.operator("arp.delete_arp", text="", icon='PANEL_CLOSE')

            col = layout.column(align=True)
            if object:
                col.enabled = rig_is_selected

                col.separator()
                col.label(text="Rig Definition:")
                col.prop(object, "arp_rig_type", text="", expand=False)

                col = layout.column(align=True)

                # normal mode display
                if prop_type != "free":
                    col = layout.column(align=True)
                    col.enabled = rig_is_selected
                    col.separator()
                    row = layout.column(align=True).row(align=True)
                    row.enabled = rig_is_selected

                layout.separator()
                if scn.arp_experimental_mode:
                    col = layout.column()
                    col.enabled = rig_is_selected
                    col.prop(object, "arp_master_fly", text="Fly Master")
                row = layout.row()
                row.enabled = rig_is_selected
                row.prop(scn, "arp_init_scale")
                row.operator('arp.show_retro_options', text="Legacy...", icon='SETTINGS')
                layout.separator()
                layout.label(text="Secondary Controllers:")
                row = layout.row()
                row.prop(object, "arp_secondary_type", text="", icon='CURVE_NCURVE')
                row.enabled = False
                if rig_is_selected:
                    row.enabled = True

                layout.separator()
                layout.separator()
                col = layout.column(align=True)
                col.enabled = object.type == "ARMATURE"
                row = col.row(align=True)
                row.operator_menu_enum('arp.add_limb', 'limbs_presets', text='Add Limb', icon='PLUS')
                row.menu('ARP_MT_add_limb_menu', text="", icon='DOWNARROW_HLT')
                col.separator()
                if rig_is_selected:
                    if context.active_object.data.layers[17]:
                        layout.prop(scn, "arp_show_ik_chain_direction", text="Show IK Directions")
                layout.operator("arp.edit_ref", text="Edit Reference Bones", icon='EDITMODE_HLT')
                layout.operator('arp.show_limb_params', text='Limb Options', icon='SETTINGS')
                row = layout.row(align=True)
                row.operator('arp.dupli_limb', text='Duplicate')
                row.operator('arp.dupli_limb_mirror', text='Dupli. Mirror')#, icon='MOD_MIRROR')
                
                row = layout.row(align=True)
                row.operator('arp.disable_limb', text='Disable', icon='X')
                
                
        if object:
            row = layout.row(align=True)
            row.operator('arp.import_rig_data_options', text='Import')
            row.operator('arp.export_rig_data_options', text='Export')
            
            if not locked_rig:
                row = layout.row(align=True)
                row.scale_y = 1.3
                row.operator("arp.match_to_rig", text="Match to Rig", icon='POSE_HLT')
                row = layout.row(align=True)

        layout.separator()
        row = layout.row(align=True)
        if bpy.context.mode != 'EDIT_MESH':
            row.operator("arp.edit_custom_shape", text="Edit Shape...")# , icon="MESH_DATA")
            row.operator("arp.mirror_custom_shape", text="", icon="MOD_MIRROR")
        else:
            layout.operator("arp.apply_shape", text="Apply Shape")

        
        if scn.arp_experimental_mode:
            row = layout.row(align=True)
            row.operator("arp.add_muscles", text="Add Muscles", icon="PLUS")
            row.operator("arp.remove_muscles", text="", icon='PANEL_CLOSE')

        
        if scn.arp_debug_mode:
            layout.separator()
            layout.label(text="Developer tools:", icon="ERROR")
            col = layout.column(align=True)
            col.operator("arp.export_limbs", text="Export Limbs")
            col.operator("arp.export_data", text="Export Data")


class ARP_PT_rig_pose(Panel, ArpRigPanel):
    bl_label = "Pose Tools"
    bl_parent_id = "ARP_PT_auto_rig_pro_panel"
    bl_options = {'DEFAULT_CLOSED'}

    @classmethod
    def poll(cls, context):
        return context.scene.arp_active_tab == 'CREATE'

    def draw(self, context):
        # if locked (Quick Rig Preserve mode), do not show interface, not supposed to be editable
        locked_rig = False
        object = context.active_object
        
        if object:
            if object.data:
                if "arp_locked" in object.data.keys():
                    locked_rig = True
                    
        layout = self.layout
        col = layout.column(align=True)
        row = col.row(align=True)
        row.operator("arp.add_fist_ctrl", text="Add Hand Fist")
        row.operator("arp.remove_fist_ctrl", text="", icon='PANEL_CLOSE')
        row = col.row(align=True)
        row.operator("arp.add_blink_pose", text="Add Blink Pose")
        row.operator("arp.remove_blink_pose", text="", icon='PANEL_CLOSE')
        
        layout.separator()
        col = layout.column(align=True)
        col.operator("arp.set_pose", text="Set Pose...")
        if not locked_rig:
            col.operator("arp.apply_pose_as_rest", text="Apply Pose as Rest Pose")
            

class ARP_PT_binding_settings(Panel, ArpRigPanel):
    bl_label = "Binding Settings"
    bl_parent_id = "ARP_PT_auto_rig_pro_panel"
    #bl_options = {'DEFAULT_CLOSED'}

    @classmethod
    def poll(cls, context):
        return context.scene.arp_active_tab == 'BIND'

    def draw(self, context):
        object = context.active_object
        scn = context.scene
        bind_valid = True

        layout = self.layout
            
        layout.prop(scn, "arp_bind_engine", text="Engine")

        if scn.arp_bind_engine == "HEAT_MAP":
            col = layout.column()
            col.separator()
            col.prop(scn, "arp_bind_split", text="Split Parts")
            col = layout.column()
            col.prop(scn, "arp_optimize_highres", text="Optimize High Res")
            col = layout.column(align=True)
            col.enabled = scn.arp_optimize_highres
            col.prop(scn, "arp_highres_threshold", text="Polycount Threshold")

        elif scn.arp_bind_engine == "PSEUDO_VOXELS":
            col = layout.column()
            col.separator()

            col.prop(scn, 'arp_pseudo_voxels_type', text="")
            col.prop(scn, 'arp_pseudo_voxels_resolution', text="Voxel Resolution")

            col = layout.column(align=True)
            col.label(text='If results are incorrect, try', icon='INFO')
            col.label(text='changing "Type" and "Voxel Resol."')

            col = layout.column()

        elif scn.arp_bind_engine == "VHDS":
            col = layout.column()
            col.separator()

            vhds_installed = False
            try:
                scn.voxel_resolution
                vhds_installed = True
            except:
                pass

            if vhds_installed:
                col.prop(scn, 'voxel_resolution', text="Voxel Resolution")
            else:
                col.label(text="Voxel Heat Diffuse Skinning", icon="ERROR")
                col.label(text="addon not installed")
                bind_valid = False

        # common settings
        col = layout.column()
        col.separator()
        if object:
            if is_facial_enabled(object) or object.arp_rig_type == 'quadruped':
                col.enabled = False
        else:
            col.enabled = True

        col.prop(scn, "arp_bind_chin")
        col = layout.column()
        col.prop(scn, "arp_bind_improve_twists")
        col.prop(scn, "arp_bind_improve_hips")
        col.prop(scn, "arp_bind_improve_heels")


class ARP_PT_binding_specials(Panel, ArpRigPanel):
    bl_label = "Specials"
    bl_parent_id = "ARP_PT_auto_rig_pro_panel"
    bl_options = {'DEFAULT_CLOSED'}

    @classmethod
    def poll(cls, context):
        return context.scene.arp_active_tab == 'BIND'

    def draw(self, context):
        scn = context.scene
        object = context.object
        layout = self.layout
        
                
        layout.label(text="Eyeballs:")
        col = layout.column(align=True)
        row = col.row()
        row.prop(scn, "arp_eyeball_type", expand=True)

        if scn.arp_eyeball_type == "SEPARATE":
            col.label(text="Left Eyeball:")
        else:
            col.label(text="Eyeball Object:")

        row = col.row(align=True)
        row.prop_search(scn, "arp_eyeball_name", bpy.data, "objects", text="")
        op = row.operator("id.smart_pick_object", text="", icon='EYEDROPPER')
        op.op_prop = "eyeball"
        if scn.arp_eyeball_type == "SEPARATE":
            col.label(text="Right Eyeball:")
            row = col.row(align=True)
            row.prop_search(scn, "arp_eyeball_name_right", bpy.data, "objects", text="")
            op = row.operator("id.smart_pick_object", text="", icon='EYEDROPPER')
            op.op_prop = "eyeball_right"

        col.separator()
        col.label(text="Eyelids Borders:")
        row = col.column().row(align=True)
        borders_action = "Set Left"
        if eyelids_borders_data.left_borders:
            borders_action="Set Right"
        if eyelids_borders_data.right_borders:
            borders_action="Clear"
        row.operator("arp.set_eyelids_borders", text=borders_action).action = borders_action


class ARP_PT_binding_bind(Panel, ArpRigPanel):
    bl_label = "Bind"
    bl_parent_id = "ARP_PT_auto_rig_pro_panel"
    #bl_options = {'DEFAULT_CLOSED'}

    @classmethod
    def poll(cls, context):
        return context.scene.arp_active_tab == 'BIND'

    def draw(self, context):
        scn = context.scene
        bind_valid = True

        vhds_installed = False
        if scn.arp_bind_engine == "VHDS":
            # test if the voxel_resolution property of the VHDS addon is found
            try:
                scn.voxel_resolution
            except:
                bind_valid = False

        layout = self.layout

        col = layout.column()
        row = col.row()
        row.prop(scn, "arp_bind_selected_bones", text="Selected Bones Only")
        if scn.arp_bind_selected_bones:
            row.prop(scn, 'arp_show_deforming', text='', icon='HIDE_OFF', emboss=True)
        
        col.prop(scn, "arp_bind_sel_verts", text="Selected Vertices Only")
        col.prop(scn, "arp_bind_preserve", text="Preserve Volume")
        col.prop(scn, "arp_bind_scale_fix", text="Scale Fix")

        layout.separator()

        col_bind = layout.column(align=True)
        row = col_bind.row(align=True)
        row.scale_y = 1.3

        if scn.arp_bind_engine == "VHDS":
            row.operator("arp.bind_vhds", text="Bind")
        else:
            row.operator("arp.bind_to_rig", text="Bind")

        row.operator("arp.unbind_to_rig", text="Unbind")
        row.enabled = bind_valid


class ARP_PT_binding_shapekeys(Panel, ArpRigPanel):
    bl_label = "Shape Keys Tools"
    bl_parent_id = "ARP_PT_auto_rig_pro_panel"
    bl_options = {'DEFAULT_CLOSED'}

    @classmethod
    def poll(cls, context):
        return context.scene.arp_active_tab == 'BIND'


    def draw(self, context):
        scn = context.scene
        object = context.active_object
        selected_objects = context.selected_objects

        #layout = self.layout
        layout = self.layout.column(align=True)
        col = layout.column()        
        
        active_armature = ""

        if len(context.selected_objects):
            if context.selected_objects[0].type == 'ARMATURE':
                active_armature = context.selected_objects[0].data.name
            else:
                if len(context.selected_objects) > 1:
                    if context.selected_objects[1].type == 'ARMATURE':
                        active_armature = context.selected_objects[1].data.name

        row = col.row(align=True)

        if context.active_object:
            if object.type == 'ARMATURE':
                row.enabled = True
        else:
            row.enabled = False

        if active_armature != "":
            row.prop_search(scn, "arp_driver_bone", bpy.data.armatures[active_armature], "bones", text="")
        row.operator("arp.pick_bone", text="", icon='EYEDROPPER')
        col = layout.column(align=True)
        col.enabled = (active_armature != "")

        col.prop(scn, "arp_driver_transform", text="")
        
        col = layout.column(align=True)
        col.operator("arp.create_driver", text="Create Driver")
        col.enabled = (len(selected_objects) == 2)        
       
        row = layout.row(align=True)       
        btn = row.operator('arp.set_shape_key_driver', text='0')
        btn.value = '0'
        btn = row.operator('arp.set_shape_key_driver', text='1')
        btn.value = '1'
        btn = row.operator('arp.set_shape_key_driver', text='Reset')
        btn.value = 'reset'

        layout.separator()
        layout.operator(ARP_OT_mirror_shape_keys.bl_idname, text="Mirror Shape Keys", icon='MOD_MIRROR')

        layout.separator()
        
        layout.label(text="Corrective Shapes:")
        
        if scn.arp_corrective_shapes_data == '':
            layout.operator("arp.add_corrective_bone", text="Pick Selected Bone(s)")
        else:
            row = layout.column().row(align=True)
            row.operator("arp.add_corrective_driver", text="Add Corrective Driver")
            row.prop(scn, "arp_show_driver_data", icon="HIDE_OFF", icon_only=True)
            layout.operator("arp.cancel_corrective_driver", text="Cancel")
            if scn.arp_show_driver_data:
                for i, stri in enumerate(scn.arp_corrective_shapes_data.split(',')):
                    if i < 3:
                        layout.label(text=stri)


class ARP_PT_misc(Panel, ArpRigPanel):
    bl_label = "Miscellaneous"
    bl_parent_id = "ARP_PT_auto_rig_pro_panel"
    #bl_options = {'DEFAULT_CLOSED'}

    @classmethod
    def poll(cls, context):
        return context.scene.arp_active_tab == 'TOOLS'


    def draw(self, context):
        scn = context.scene
        object = context.active_object
        layout = self.layout
        
        # if locked (Quick Rig Preserve mode), do not show interface, not supposed to be editable
        if object:
            if object.data:
                if "arp_locked" in object.data.keys():                    
                    return
                    
        col = layout.column()
        # Bug, using layout.operator() prevents correct display when the panel is shown for the first time.
        # Use colmun() instead
        col.operator("arp.update_armature", text="Update Armature")
        col.operator("arp.check_for_update", text="Check for Updates")        
        col.operator("arp.clean_scene", text="Clean Scene")
        col.operator("arp.set_character_name", text="Set Character Name")


class ARP_PT_misc_picker(Panel, ArpRigPanel):
    bl_label = "Picker Panel"
    bl_parent_id = "ARP_PT_auto_rig_pro_panel"
    #bl_options = {'DEFAULT_CLOSED'}

    @classmethod
    def poll(cls, context):
        return context.scene.arp_active_tab == 'TOOLS'


    def draw(self, context):
        scn = context.scene
        layout = self.layout
        col = layout.column(align=True)
        row = col.row(align=True)
        row.operator("arp.add_picker", text="Add Picker")  # , icon = 'PLUS')
        row.operator("arp.remove_picker", text="", icon='PANEL_CLOSE')
        row = col.row(align=True)
        row.operator("arp.import_picker", text="Import")
        row.operator("arp.export_picker", text="Export")
        col.operator("arp.set_picker_camera", text="Set Picker Cam")# , icon = 'CAMERA_DATA')

        row = col.row(align=True)
        row.operator("arp.screenshot_head_picker", text="Capture Facial")  # , icon='RENDER_STILL')
        row = col.row(align=True)
        if len(context.scene.keys()) > 0:
            proxy_picker_found = True
            try:
                context.scene.Proxy_Picker.active
            except:
                proxy_picker_found = False

            if proxy_picker_found:
                if context.scene.Proxy_Picker.active:
                    btn = row.operator("arp.move_picker_layout", text="Edit Layout...")
                    btn.state = 'start'
                else:
                    btn = row.operator("arp.move_picker_layout", text="Apply Layout")
                    btn.state = 'end'

        row = col.row(align=True)
        row.operator("arp.mirror_picker", text="Mirror")# icon = 'MOD_MIRROR'


class ARP_PT_misc_color(Panel, ArpRigPanel):
    bl_label = "Color Theme"
    bl_parent_id = "ARP_PT_auto_rig_pro_panel"
    #bl_options = {'DEFAULT_CLOSED'}

    @classmethod
    def poll(cls, context):
        return context.scene.arp_active_tab == 'TOOLS'


    def draw(self, context):
        scn = context.scene

        layout = self.layout
        col = layout.column(align=True)
        row = col.row(align=True)
        row.prop(scn, "color_set_right", text="")
        row.prop(scn, "color_set_middle", text="")
        row.prop(scn, "color_set_left", text="")
        row = col.row(align=True)
        row.prop(scn, "color_set_panel", text="")
        row.prop(scn, "color_set_text", text="")
        row = col.row(align=True)
        row.operator("arp.assign_colors", text="Assign")
        row = col.row(align=True)
        row.operator("arp.import_colors", text="Import")
        row.operator("arp.export_colors", text="Export")



@persistent
def cleanup_line_fx(dummy):
    try:
        bpy.types.SpaceView3D.draw_handler_remove(handles_lines[0], 'WINDOW')
        if bpy.context.scene.arp_debug_mode:
            print('Removed handler')
    except:
        if bpy.context.scene.arp_debug_mode:
            print('No handler to remove')
            

##################  REGISTER  ##################


arp_custom_icons = None

bpy.app.handlers.load_pre.append(cleanup_line_fx)
classes = (
    ARP_OT_report_message, ARP_OT_show_limb_params, ARP_OT_export_limbs, ARP_OT_export_data, ARP_OT_remove_picker,
    ARP_OT_add_picker, ARP_OT_import_picker, ARP_OT_export_picker, ARP_OT_add_fist_ctrl, ARP_OT_remove_fist_ctrl,
    ARP_OT_add_blink_pose, ARP_OT_remove_blink_pose,
    ARP_OT_mirror_picker, ARP_OT_move_picker_layout, ARP_OT_screenshot_head_picker, ARP_OT_assign_colors,
    ARP_OT_clean_skin, ARP_OT_delete_arp, ARP_OT_append_arp, ARP_OT_apply_shape, ARP_OT_edit_custom_shape,
    ARP_OT_mirror_custom_shape, ARP_OT_import_colors, ARP_OT_export_colors, ARP_OT_export_rig_data,
    ARP_OT_export_rig_data_options, ARP_OT_import_rig_data_options, ARP_OT_import_rig_data, ARP_OT_disable_limb,  ARP_OT_update_armature, ARP_OT_check_for_update,
    ARP_OT_set_shape_key_driver, ARP_OT_pick_bone, ARP_OT_create_driver, ARP_OT_set_picker_camera, ARP_OT_bind_to_rig,
    ARP_OT_unbind_to_rig, ARP_OT_edit_ref, ARP_OT_add_limb, ARP_OT_save_limb, ARP_OT_remove_custom_limbs, ARP_OT_exec_rem_custom_limbs, ARP_MT_add_limb_menu,
    ARP_OT_dupli_limb, ARP_OT_dupli_limb_mirror, ARP_OT_match_to_rig, ARP_OT_clean_scene, ARP_OT_apply_pose_as_rest, ARP_OT_set_pose,
    ARP_OT_set_character_name, ARP_OT_show_retro_options, ARP_OT_align_wings, ARP_OT_add_muscles, ARP_OT_remove_muscles,
    ARP_OT_add_corrective_bone, ARP_OT_add_corrective_driver, ARP_OT_cancel_corrective_driver, ARP_OT_lines_fx,
    ARP_OT_smart_pick_object, ARP_OT_set_eyelids_borders, ARP_OT_mirror_shape_keys,
    ARP_OT_bind_VHDS, ARP_PT_auto_rig_pro_panel, ARP_PT_rig_pose, ARP_PT_binding_settings, ARP_PT_binding_specials, ARP_PT_binding_bind, ARP_PT_binding_shapekeys, 
    ARP_PT_misc, ARP_PT_misc_picker, ARP_PT_misc_color, ARP_OT_toggle_action_preserve_pose, ARP_OT_preserve_enable_all_actions, ARP_OT_preserve_disable_all_actions
    )

def update_arp_tab():
    interface_classes = (ARP_PT_auto_rig_pro_panel, ARP_PT_rig_pose, ARP_PT_binding_settings, ARP_PT_binding_specials, ARP_PT_binding_bind, ARP_PT_binding_shapekeys, ARP_PT_misc, ARP_PT_misc_picker, ARP_PT_misc_color)

    for cl in reversed(interface_classes):
        try:
            bpy.utils.unregister_class(cl)
        except:
            pass

    ArpRigPanel.bl_category = bpy.context.preferences.addons[__package__].preferences.arp_tab_name

    for cl in interface_classes:
        bpy.utils.register_class(cl)
 

def update_armatures_presets():
    # print("  look for custom armature presets...")
    addon_directory = os.path.dirname(os.path.abspath(__file__))
    presets_path = addon_directory + "/armature_presets"
    default_presets = ["bird", "dog", "free", "horse", "human", "master", "modules"]
    add_separator = False

    for file in os.listdir(presets_path):
        if file.endswith(".blend"):
            if file.replace(".blend", "") not in default_presets:
                preset_name = file.replace('.blend', '')
                preset_display_name = preset_name.replace('_', ' ').title()
                # print("  found preset:", preset_name)
                if not (preset_name, preset_display_name,
                        preset_display_name + ' armature') in ARP_OT_append_arp.rig_presets_items:
                    # add a line as visual separator in the list, to differentiate user added presets
                    if add_separator == False:
                        add_separator = True
                        ARP_OT_append_arp.rig_presets_items.append(('____', '____', '____'))
                    ARP_OT_append_arp.rig_presets_items.append(
                        (preset_name, preset_display_name, preset_display_name + ' armature'))
                        
                        
def update_show_def_layer(self, context):
    scn = bpy.context.scene
    arm = bpy.context.active_object
    if arm.type == 'ARMATURE':
        if scn.arp_show_deforming:
            # store current
            scn.arp_show_def_store = [arm.data.layers[i] for i in range(0,32)]
            # show deform
            arm.data.layers[31] = True
            for i in range(0,31):
                arm.data.layers[i] = False
        else:            
            for i in range(0,32):
                arm.data.layers[i] = scn.arp_show_def_store[i]
            arm.data.layers[31] = False
                

def register():
    from bpy.utils import register_class

    for cls in classes:
        try:  # cache bug, may happen after addon update... silly workaround
            register_class(cls)
        except:
            pass

    # set the UI in the user defined tab
    update_arp_tab()
    # scan the armature presets directory for user defined presets
    update_armatures_presets()
    update_limbs_presets()

    # custom icons
    global arp_custom_icons
    arp_custom_icons = bpy.utils.previews.new()
    icons_dir = os.path.join(os.path.dirname(__file__), "icons")
    arp_custom_icons.load("fist", os.path.join(icons_dir, "fist.png"), 'IMAGE')

    bpy.types.Object.arp_rig_type = EnumProperty(items=(
        ('biped', 'Biped', 'Biped Rig Type, vertical spine orientation'),
        ('quadruped', 'Multi-Ped', 'Multi-Ped rig type, free spine orientation')), name="Rig Type",
        description="Rig type to define the spine controllers orientation")
    bpy.types.Object.rig_fingers_rot = EnumProperty(items=(('scale_2_phalanges', 'Rot from Scale: 2',
                                                                      'The mid and tip phalanges rotation are driven by the scale of the first one'),
                                                                     ('scale_3_phalanges', 'Rot from Scale: 3',
                                                                      'All phalanges rotation are driven by the scale of the first one'),
                                                                     ('no_scale', 'Disabled',
                                                                      'Phalanges rotations are not driven by the scale of the first one')),
                                                              name="Fingers Rotation",
                                                              description="Automatic rotation of the fingers phalanges based on the scale of the first phalange")
    bpy.types.Object.rig_fingers_rot_thumb = EnumProperty(items=(('scale_2_phalanges', 'Rot from Scale: 2',
                                                                      'The mid and tip phalanges rotation are driven by the scale of the first one'),
                                                                     ('scale_3_phalanges', 'Rot from Scale: 3',
                                                                      'All phalanges rotation are driven by the scale of the first one'),
                                                                     ('no_scale', 'Disabled',
                                                                      'Phalanges rotations are not driven by the scale of the first one')),
                                                              name="Fingers Rotation",
                                                              description="Automatic rotation of the thumb phalanges based on the scale of the first phalange")
    bpy.types.Object.arp_secondary_type = EnumProperty(items=(('ADDITIVE', 'Additive (Exportable)',
                                                                         'Additive mode for the secondary deformations used to curve the arms and legs.\nExportable to FBX'),
                                                                        ('TWIST_BASED', 'Twist (Exportable, best)',
                                                                         'Twist based mode for the secondary controllers used to curve the arms and legs, for optimal shape control. \nBest compatibility with Fbx Export.\nWorks best with multiple twist bones.'),
                                                                        ('BENDY_BONES', 'Bendy Bones',
                                                                         'Bendy bones for the secondary deformations used to curve the arms and legs.\nGood for very stretchy/cartoony characters.\nWarning, the secondary controllers and twist bones will not be exportable to FBX (Advanced option)'),
                                                                        ('NONE', 'None', 'No secondary controllers')),
                                                                 name="Secondary Deformations",
                                                                 description="Deformation mode for the secondary and twist bones of the arms and legs. Applied after Match to Rig.",
                                                                 default="NONE", update=update_secondary_controllers)
    bpy.types.Object.arp_fingers_shape_style = EnumProperty(items=(
        ('circle', 'Circle', 'Set circle shapes', 'MESH_CIRCLE', 1), ('box', 'Box', 'Set box shapes', 'MESH_CUBE', 2)),
        name="Finger Shapes", description="Default shapes of the fingers controllers, if not already edited\nApplied after Match to Rig")

    bpy.types.Scene.arp_init_scale = BoolProperty(name="Init Scale", default=True, description="Initialize the armature scale (1) after Match to Rig")
    bpy.types.Object.rig_spine_count = IntProperty(default=3, min=1, max=32, description='Number of spine bones')    
    bpy.types.Object.arp_master_fly = BoolProperty(default=False, description='Add a Fly master controller')
    bpy.types.Scene.arp_driver_bone = StringProperty(name="Bone Name",
                                                               description="Bone driving the shape key")
    bpy.types.Scene.arp_driver_transform = EnumProperty(items=(
        ('LOC_X', 'Loc X', 'X Location'), ('LOC_Y', 'Loc Y', 'Y Location'), ('LOC_Z', 'Loc Z', 'Z Location'),
        ('ROT_X', 'Rot X', 'X Rotation'), ('ROT_Y', 'Rot Y', 'Y Rotation'), ('ROT_Z', 'Rot Z', 'Z Rotation'),
        ('SCALE_X', 'Scale X', 'X Scale'), ('SCALE_Y', 'Scale Y', 'Y Scale'), ('SCALE_Z', 'Scale Z', 'Z Scale')),
        name="Bone Transform")
    bpy.types.Scene.arp_highres_threshold = IntProperty(name="High Res Threshold",
                                                                  description="Meshes with polycount higher than this will be considered as high resolution meshes to optimize binding performances",
                                                                  default=70000)
    bpy.types.Scene.arp_optimize_highres = BoolProperty(name="Optimize High Resolution Meshes",
                                                                  description="Speed up binding time of high resolution meshes",
                                                                  default=False)
    bpy.types.Scene.arp_pseudo_voxels_resolution = IntProperty(name="Pseudo-Voxels Resolution",
                                                                         description="Voxel precision, higher values is more accurate but longer to compute.\nLow values may sometimes work better than high values depending on the mesh complexity.",
                                                                         default=8,
                                                                         min=3, max=12)
    bpy.types.Scene.arp_pseudo_voxels_type = EnumProperty(name="Pseudo-Voxels Type", description="Voxelization engine", items=(('1', 'Type 1', 'Type 1'), ('2', 'Type 2', 'Type2')))

    bpy.types.Scene.arp_bind_engine = EnumProperty(items=(('HEAT_MAP', 'Heat Maps', 'Surface skinning.\nAccurate but may be inconsistent with multiple layers of clothes, props'), 
                                                        ('PSEUDO_VOXELS', 'Voxelized', 'Approximated volume skinning. \nMore consistent than Heat Maps, but less accurate with small details (fingers, facial...)'), 
                                                        ('VHDS', 'Voxel Heat Diffuse Skinning (addon)', 'True volume skinning.\nRequires the Voxel Heat Diffuse Skinning addon installed')), 
                                                        description="Binding method", name="Bind Engine")
    bpy.types.Scene.arp_bind_improve_twists = BoolProperty(default=True, name="Smooth Twist Weights", description="Refine twist weights by applying a gradient decay along the bone")
    bpy.types.Scene.arp_bind_improve_hips = BoolProperty(default=True, name="Improve Hips Weights", description="Improve hips weights")
    bpy.types.Scene.arp_bind_improve_heels = BoolProperty(default=True, name="Improve Heels Weights", description="Improve foot, heel weights")
    bpy.types.Scene.arp_bind_split = BoolProperty(default=True,
                                                            description="Improve skinning by separating the loose parts (e.g: hats, buttons, belt...) before binding.\nWarning: meshes with a lot of separate pieces can take several minutes to bind.")
    bpy.types.Scene.arp_bind_chin = BoolProperty(default=True,
                                                           description="Improve head skinning based on the chin position, defined by Smart or approximated.\nOnly when facial is disabled, and biped type", name="Refine Head Weights")
    bpy.types.Scene.arp_bind_preserve = BoolProperty(default=True,
                                                               description="Enables the Preserve Volume option of the armature modifier (dual quaternions skinning).\nShould be disabled when exporting to game engines")
    bpy.types.Scene.arp_bind_scale_fix = BoolProperty(default=False,
                                                                description="Check this if binding did not work.\nInternally operate on a scaled version of the meshes to fix defective skinning")
    bpy.types.Scene.arp_bind_sel_verts = BoolProperty(default=False, description="Only bind selected vertices")
    bpy.types.Scene.arp_bind_selected_bones = BoolProperty(default=False, description="Only bind to selected deforming bones")
    bpy.types.Scene.arp_active_tab = EnumProperty(
        items=(('CREATE', 'Rig', 'Create Tab'), ('BIND', 'Skin', 'Bind Tab'), ('TOOLS', 'Misc', 'Misc Tab')))
    bpy.types.Scene.arp_show_deforming = BoolProperty(default=False, description="Show the deforming bones layer temporarily to select deforming bones", update=update_show_def_layer)
    bpy.types.Scene.arp_show_def_store = BoolVectorProperty(size=32, default=(False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False), subtype='LAYER')
    bpy.types.Scene.color_set_right = FloatVectorProperty(name="Color Right", subtype="COLOR_GAMMA",
                                                                    default=(0.602, 0.667, 1.0), min=0.0, max=1.0,
                                                                    description="Right controllers color")
    bpy.types.Scene.color_set_middle = FloatVectorProperty(name="Color Middle", subtype="COLOR_GAMMA",
                                                                     default=(0.205, 0.860, 0.860), min=0.0, max=1.0,
                                                                     description="Middle controllers color")
    bpy.types.Scene.color_set_left = FloatVectorProperty(name="Color Left", subtype="COLOR_GAMMA",
                                                                   default=(0.8, 0.432, 0.0), min=0.0, max=1.0,
                                                                   description="Left controllers color")
    bpy.types.Scene.color_set_panel = FloatVectorProperty(name="Color Panel", subtype="COLOR_GAMMA",
                                                                    default=(0.2, 0.2, 0.2), min=0.0, max=1.0,
                                                                    description="Back picker panel color")
    bpy.types.Scene.color_set_text = FloatVectorProperty(name="Color Text", subtype="COLOR_GAMMA",
                                                                   default=(0.887, 0.887, 0.887), min=0.0, max=1.0,
                                                                   description="Text color in the picker panel")
    bpy.types.Scene.arp_retro_constraints = BoolProperty(name="Retro Constraints", description="Use old constraints system which may change the feet roll/bank and eyelids animation\nRelevant if unusual rig scale was used, or unusual unit scale such as 0.01, to preserve previous animations", default=False)
    bpy.types.Scene.arp_retro_feet = BoolProperty(name="Retro Feet", description="Use old non-symmetrized feet roll", default=False)
    bpy.types.Scene.arp_retro_eyes = BoolProperty(name="Retro Eyes", description="Use old eyes alignment system which leads to issues with non-parallel or rotated eyes", default=False)
    bpy.types.Scene.arp_retro_spine_bend = BoolProperty(name="Retro Spine Bend", description="Use old method to align spine bend bones. Not recommended unless when using old files.", default=False)
    bpy.types.Scene.arp_retro_action_prefix = BoolProperty(name="Retro Action Prefix", description="Old 'mp_' and 'h_' actions prefix for export", default=False)
    bpy.types.Scene.arp_retro_export_soft_fix = BoolProperty(name="Retro Soft Link Positions", description="Old thigh and arm bones position when enabling Soft-Link for export", default=False)
    bpy.types.Scene.arp_retro_splineik_quat = BoolProperty(name="Retro Spline IK Quat", description="Use quaternions rotation for Spline IK controllers", default=False)
    bpy.types.Scene.arp_retro_stretch_pin = BoolProperty(name="Retro Stretch and Pin", description="Old alignment for the stretch and pin controllers of arms and legs", default=False)
    bpy.types.Scene.arp_retro_axes = BoolProperty(name="Retro Axes", description="Use old axes alignment, not consistent", default=False)
    bpy.types.Scene.arp_retro_lips = BoolProperty(name="Retro Auto-Lips", description="Use old auto-lips constraints")
    bpy.types.Scene.arp_retro_ge_mesh = BoolProperty(name="Retro Mesh Names", description="Use duplicate mesh names (.001) when exporting to Fbx", default=False)
    bpy.types.Scene.arp_corrective_shapes_data = StringProperty(name="", default="")
    bpy.types.Scene.arp_show_driver_data = BoolProperty(name="Show Driver Data", description="Display the bones name and bone angle (in radians) used to set up the rotational difference driver", default=False)
    bpy.types.Scene.arp_show_ik_chain_direction = BoolProperty(name="Show IK Chain Direction", description="Draw a line to show the IK direction of the selected leg or arm limb", default=False, update=update_show_ik_lines)
    bpy.types.Scene.arp_eyeball_type = EnumProperty(name="Eyeballs Type", description = "Type of the eyeballs object: single object for left and right, or 2 separate objects", items=(('SINGLE', 'One', 'Single object for both left and right eyeballs'), ('SEPARATE', 'Two', 'Two separate objects for left and right eyeballs')))
    bpy.types.Scene.arp_eyeball_name = StringProperty(name="Eyeball object", description = "Name of the eyeball object")
    bpy.types.Scene.arp_eyeball_name_right = StringProperty(name="Right eyeball object", description = "Name of the right eyeball object")   


def unregister():
    from bpy.utils import unregister_class

    for cls in reversed(classes):
        unregister_class(cls)

        # custom icons
    global arp_custom_icons
    bpy.utils.previews.remove(arp_custom_icons)

    del bpy.types.Object.arp_rig_type
    del bpy.types.Object.rig_fingers_rot
    del bpy.types.Object.rig_fingers_rot_thumb
    del bpy.types.Object.arp_secondary_type
    del bpy.types.Object.arp_fingers_shape_style
    del bpy.types.Scene.arp_init_scale
    del bpy.types.Object.rig_spine_count    
    del bpy.types.Object.arp_master_fly
    del bpy.types.Scene.arp_driver_bone
    del bpy.types.Scene.arp_driver_transform

    del bpy.types.Scene.arp_optimize_highres
    del bpy.types.Scene.arp_highres_threshold
    del bpy.types.Scene.arp_pseudo_voxels_resolution
    del bpy.types.Scene.arp_pseudo_voxels_type
    del bpy.types.Scene.arp_bind_engine
    del bpy.types.Scene.arp_bind_improve_hips
    del bpy.types.Scene.arp_bind_improve_twists
    del bpy.types.Scene.arp_bind_improve_heels
    del bpy.types.Scene.arp_bind_split
    del bpy.types.Scene.arp_bind_chin
    del bpy.types.Scene.arp_bind_preserve
    del bpy.types.Scene.arp_bind_scale_fix
    del bpy.types.Scene.arp_bind_sel_verts
    del bpy.types.Scene.arp_active_tab
    del bpy.types.Scene.arp_show_deforming
    del bpy.types.Scene.arp_show_def_store
    del bpy.types.Scene.color_set_right
    del bpy.types.Scene.color_set_middle
    del bpy.types.Scene.color_set_left
    del bpy.types.Scene.color_set_panel
    del bpy.types.Scene.color_set_text
    del bpy.types.Scene.arp_retro_constraints
    del bpy.types.Scene.arp_retro_feet
    del bpy.types.Scene.arp_retro_eyes
    del bpy.types.Scene.arp_retro_spine_bend
    del bpy.types.Scene.arp_retro_action_prefix
    del bpy.types.Scene.arp_retro_splineik_quat
    del bpy.types.Scene.arp_retro_stretch_pin
    del bpy.types.Scene.arp_retro_axes
    del bpy.types.Scene.arp_retro_lips
    del bpy.types.Scene.arp_retro_ge_mesh
    del bpy.types.Scene.arp_corrective_shapes_data
    del bpy.types.Scene.arp_show_driver_data
    del bpy.types.Scene.arp_show_ik_chain_direction
    del bpy.types.Scene.arp_eyeball_type
    del bpy.types.Scene.arp_eyeball_name
    del bpy.types.Scene.arp_eyeball_name_right