
bl_info={
    "name":"FREEIK",
    "category":"Rigging",
    "version":(1,2,0),
    "blender":(2,80,0),
    "location":"",
    "description":"More intuitive way to rig and animate",
    "wiki_url":"https://xbodya13.github.io/free_ik_doc/",
    "tracker_url":"https://github.com/xbodya13/free_ik_doc/issues"
}

import bpy
import bgl
import blf
import gpu
import mathutils
import rna_prop_ui


import itertools
import time
import random
import math
import copy
import numpy as np
import os
import hashlib


from gpu_extras.batch import batch_for_shader
from bpy.app.handlers import persistent
from typing import *



class gv:
    keymaps=None
    keyconfig=None
    time_to_make_keys=False

    scene=False
    frame=False
    render=False

    before=False
    after=False


    class names:
        prime_name="free_ik"

        constraint_collection_name=prime_name+"_constraint_collection"

        pose_parent_name=prime_name+"_pose_parent"
        frame_parent_name=prime_name+"_frame_parent"

        smooth='SMOOTH'
        rope='ROPE'
        stretch='STRETCH'
        solver_mode=smooth

        stretch_head='STRETCH_HEAD'
        stretch_tail='STRETCH_TAIL'
        stretch_both='STRETCH_BOTH'
    class operator:

        is_paste_pose=False
        paste_full=False
        paste_partial=False

        is_modal_transform=False
        modal_transform_start=False
        modal_transform_cancel=False
        modal_transform_confirm=False


        operator=None
        last_operator=None
        operator_changed=False


        is_clear_translation=False
        is_clear_rotation=False
        is_clear_scale=False
        is_clear_pose=False

        is_pose_library=False
        is_flip_quaternion=False
        is_key_create=False
        is_key_delete=False



        was_clear_location=False
        was_clear_rotation=False
        was_clear_scale=False


        @classmethod
        def update(cls):

            cls.operator_changed=bpy.context.active_operator!=cls.operator


            cls.last_operator=cls.operator
            cls.operator=bpy.context.active_operator




            cls.is_clear_translation=False
            cls.is_clear_rotation=False
            cls.is_clear_scale=False
            cls.is_clear_pose=False

            cls.is_pose_library=False
            cls.is_flip_quaternion=False
            cls.is_key_create=False
            cls.is_key_delete=False

            if cls.operator_changed and cls.operator is not None:


                if cls.operator.bl_idname=='POSELIB_OT_apply_pose': cls.is_pose_library=True
                if cls.operator.bl_idname=='POSE_OT_quaternions_flip': cls.is_flip_quaternion=True
                if cls.operator.bl_idname=='ANIM_OT_keyframe_insert_menu': cls.is_pose_library=True
                if cls.operator.bl_idname in ('ACTION_OT_delete','GRAPH_OT_delete'): cls.is_key_delete=True

                if cls.operator.bl_idname=='POSE_OT_loc_clear': cls.is_clear_translation=cls.was_clear_location=True
                if cls.operator.bl_idname=='POSE_OT_rot_clear': cls.is_clear_rotation=cls.was_clear_rotation=True
                if cls.operator.bl_idname=='POSE_OT_scale_clear': cls.is_clear_scale=cls.was_clear_scale=True

                cls.is_clear_pose=cls.is_clear_translation or cls.is_clear_rotation or cls.is_clear_scale



            if cls.operator_changed and not cls.is_clear_pose:
                cls.was_clear_location=False
                cls.was_clear_rotation=False
                cls.was_clear_scale=False
        @classmethod
        def print_state(cls):
            print("OPERATOR STATE")
            print()

            print(cls.last_operator,cls.operator,cls.operator_changed)

            print()
            print("is_clear_translation",cls.is_clear_translation)
            print("is_clear_rotation",cls.is_clear_rotation)
            print("is_clear_scale",cls.is_clear_scale)
            print("is_clear_pose",cls.is_clear_pose)

            print()
            print("is_pose_library",cls.is_pose_library)
            print("is_flip_quaternion",cls.is_flip_quaternion)
            print("is_key_create",cls.is_key_create)
            print("is_key_delete",cls.is_key_delete)

            print()
            print("was_clear_location",cls.was_clear_location)
            print("was_clear_rotation",cls.was_clear_rotation)
            print("was_clear_scale",cls.was_clear_scale)

            print()
            print("is_paste_pose",cls.is_paste_pose)
            print("paste_full",cls.paste_full)
            print("paste_partial",cls.paste_partial)

    class structure:

        item_set=None


        class last:
            item_set=None


        class change:
            item_set=False

        @classmethod
        def get_item_set(cls):
            items=set()
            for item in bpy.data.objects:
                if type(item.data) is bpy.types.Armature:
                    for bone in item.pose.bones:
                        if bone.free_ik.type=='NODE':
                            items.add(bone)

                if item.free_ik.type=='NODE':
                    items.add(item)
                if item.free_ik.type=='LINK':
                    if None not in (item.free_ik.node_a,item.free_ik.node_b):
                        items.add(item)

            return items


        class Link:
            class Last:
                def __init__(self,node):
                    self.is_enabled=node.is_enabled
            class Change:
                def __init__(self):
                    self.is_enabled=False



            def __init__(self,source):
                self.source=source
                self.node_a=source.free_ik.node_a
                self.node_b=source.free_ik.node_b
                self.priority=self.source.free_ik.priority

                self.local_matrix_a=source.free_ik.local_matrix_a
                self.local_matrix_b=source.free_ik.local_matrix_b

                self.is_enabled=False

                self.last=self.Last(self)
                self.change=self.Change()


            def __str__(self): return self.source.name
            def __repr__(self): return self.__str__()

        class Node:
            class Transform:
                def __init__(self):
                    self.location=mathutils.Vector()
                    self.rotation=mathutils.Quaternion()
                    self.scale=mathutils.Vector((1,1,1))


                def compare(self,other,change):
                    change.location=self.location!=other.location
                    change.rotation=self.rotation!=other.rotation
                    change.scale=self.scale!=other.scale

                    change.any=change.location or change.rotation or change.scale
                    change.location_only=change.location and not (change.rotation or change.scale)


                def from_matrix(self,matrix,reference):
                    l,q,s=matrix.decompose()
                    if type(reference.rotation) is mathutils.Quaternion:
                        r=q
                    elif type(reference.rotation) is mathutils.Vector:
                        r=mathutils.Vector((q.angle,q.axis[0],q.axis[1],q.axis[2]))
                    else:
                        r=q.to_euler()

                    self.location=l
                    self.rotation=r
                    self.scale=s

                def __iter__(self):
                    yield self.location
                    yield self.rotation
                    yield self.scale


                def __eq__(self,other):
                    return self.location==other.location and self.rotation==other.rotation and self.scale==other.scale
            class Last:
                def __init__(self,node):
                    self.is_enabled=node.is_enabled
                    self.in_transform=gv.structure.Node.Transform()
                    self.out_transform=gv.structure.Node.Transform()
                    self.matrix=node.matrix

                    self.in_matrix=node.in_matrix
                    self.out_matrix=node.out_matrix
            class Change:
                class Transform:
                    def __init__(self):
                        self.location=False
                        self.rotation=False
                        self.scale=False
                        self.any=False
                        self.location_only=False
                    def __str__(self):
                        return f"{self.location} {self.rotation} {self.scale}"
                    def __repr__(self):return self.__str__()
                    def __iter__(self):
                        yield self.location
                        yield self.rotation
                        yield self.scale
                def __init__(self):
                    self.is_enabled=False
                    self.in_transform=self.Transform()
                    self.matrix=False

                    self.in_matrix=False
                    self.out_matrix=False



            def __init__(self,source):
                self.source=source

                self.is_bone=type(self.source) is bpy.types.PoseBone

                self.priority=self.source.free_ik.priority

                self.is_enabled=self.get_is_enabled()
                self.is_pinned=self.get_is_pinned()

                self.is_constrained=self.get_is_constrained()


                self.in_transform=self.get_transform()
                self.out_transform=self.get_transform()
                self.reference_transform=self.get_transform()
                self.modal_start_transform=self.get_transform()





                self.matrix=self.get_matrix()
                self.in_matrix=self.get_matrix()
                self.out_matrix=self.get_matrix()
                self.modal_start_matrix=self.get_matrix()



                self.frame_parent=self.get_pose_parent()
                self.pose_parent=self.get_frame_parent()

                self.last=self.Last(self)
                self.change=self.Change()

            def __str__(self):return self.source.name
            def __repr__(self):return self.__str__()



            def get_matrix(self):
                if self.is_bone:
                    return self.source.id_data.matrix_basis@self.source.matrix
                else:
                    return self.source.matrix_world.copy()
            def get_frame_parent_matrix(self):
                return self.source.free_ik_frame_parent_matrix.copy()

            def apply_matrix(self,matrix):
                if self.is_bone:
                    self.source.matrix_basis=(self.source.id_data.matrix_basis@self.source.bone.matrix_local).inverted()@matrix
                else:
                    self.source.matrix_basis=matrix
            def get_transform(self):
                t=self.Transform()
                t.location=self.source.location.copy()
                if self.source.rotation_mode=='QUATERNION':t.rotation=self.source.rotation_quaternion.copy()
                elif self.source.rotation_mode=='AXIS_ANGLE':t.rotation=mathutils.Vector(self.source.rotation_axis_angle)
                else:t.rotation=self.source.rotation_euler.copy()
                t.scale=self.source.scale.copy()

                return t

            def apply_transform(self,transform):
                self.source.location=transform.location
                if type(transform.rotation) is mathutils.Quaternion:
                    self.source.rotation_quaternion=transform.rotation
                elif type(transform.rotation) is mathutils.Vector:
                    self.source.rotation_axis_angle=transform.rotation
                else:
                    self.source.rotation_euler=transform.rotation
                self.source.scale=transform.scale



            def get_local_transform(self):
                t=self.Transform()
                t.location=self.source.free_ik_local_location.copy()
                if self.source.rotation_mode=='QUATERNION':
                    t.rotation=self.source.free_ik_local_quaternion.copy()
                elif self.source.rotation_mode=='AXIS_ANGLE':
                    t.rotation=mathutils.Vector(self.source.free_ik_local_axis_angle)
                else:
                    t.rotation=self.source.free_ik_local_euler.copy()
                t.scale=self.source.free_ik_local_scale.copy()

                return t

            def apply_local_transform(self,transform):
                self.source.free_ik_local_location=transform.location
                if type(transform.rotation) is mathutils.Quaternion:
                    self.source.free_ik_local_quaternion=transform.rotation
                elif type(transform.rotation) is mathutils.Vector:
                    self.source.free_ik_local_axis_angle=transform.rotation
                else:
                    self.source.free_ik_local_euler=transform.rotation
                self.source.free_ik_local_scale=transform.scale

            def get_is_enabled(self):
                return self.source.free_ik_is_enabled

            def get_is_pinned(self):
                return self.source.free_ik_is_pinned

            def get_is_selected(self):
                if self.is_bone:
                    return self.source.bone.select
                else:return self.source.select_get()

            def get_is_constrained(self):
                for constraint in self.source.constraints:
                    if not constraint.mute:return True
                return False

            def get_pose_parent(self):
                item=item_from_constraint(self.source,gv.names.pose_parent_name)
                if item in gv.structure.item_node:return gv.structure.item_node[item]
                else: return None


            def get_frame_parent(self):
                if self.source.free_ik_frame_parent_id in gv.structure.id_node:
                    return gv.structure.id_node[self.source.free_ik_frame_parent_id]



        item_node={}
        id_item={}
        id_node={}

        nodes=[]
        links=[]

        @classmethod
        def make(cls):
            print("MAKE STRUCTURE")

            cls.item_node={}
            cls.id_item={}
            cls.id_node={}

            cls.nodes=[]
            cls.links=[]

            items=list(cls.get_item_set())
            items.sort(key=lambda item:item.free_ik.priority)


            for item in items:
                cls.id_item[item.free_ik.id]=item
                if item.free_ik.type=='NODE':
                    node=cls.Node(item)
                    cls.nodes.append(node)
                    cls.id_node[item.free_ik.id]=node
                    cls.item_node[item]=node

                if item.free_ik.type=='LINK':
                    link=cls.Link(item)
                    cls.links.append(link)
                    cls.id_node[item.free_ik.id]=link
                    cls.item_node[item]=link

            for link in cls.links:
                link.node_a=cls.item_node[link.node_a]
                link.node_b=cls.item_node[link.node_b]



            cls.print()

        existed_ids=set()

        @classmethod
        def update_ids(cls):
            cls.existed_ids=set()
            for item in bpy.data.objects:
                if type(item.data) is bpy.types.Armature:
                    for bone in item.pose.bones:
                        cls.existed_ids.add(bone.free_ik.id)
                cls.existed_ids.add(item.free_ik.id)


        @classmethod
        def make_id(cls,item):
            out=random.random()
            while out in cls.existed_ids:
                out=random.random()
            item.free_ik.id=out
            cls.existed_ids.add(out)
            return out

        @classmethod
        def print(cls):
            print("STRUCTURE")
            print(cls.id_node)
            print(cls.id_item)
            print()
            print("NODES")
            for node in cls.nodes :print("    ",node)
            print("LINKS")
            for link in cls.links :print("    ",f"{link}{link.node_a}{link.node_b}")





    class view:
        points=[]
        colors=[]
        lines=[]



def link_duplicates(sources,duplicates,names):

    source_duplicate_dictionary={}
    for source,duplicate in zip(sources,duplicates):
        source_duplicate_dictionary[source]=duplicate
    for duplicate in duplicates:


        for name in names:
            if hasattr(duplicate,name):

                duplicate_property=getattr(duplicate,name)
                if type(duplicate_property) is list:
                    setattr(duplicate,name,[source_duplicate_dictionary[source] for source in duplicate_property if source in source_duplicate_dictionary])
                else:
                    if duplicate_property in source_duplicate_dictionary:
                        setattr(duplicate,name,source_duplicate_dictionary[duplicate_property])

class Graph:
    class Node:
        def __init__(self):


            self.is_used=False

            self.is_end=False
            self.is_beam=False
            self.is_joint=False

            self.links=[]
            self.linked_nodes=[]

    class IteratorState:
        def __init__(self):
            self.head=None
            self.tail=None
            self.link=None

    def __call__(self, item):
        return self.extenders[item]


    def __init__(self,nodes,links):
        self.nodes=list(nodes)
        self.links=list(links)

        self.extenders={node:self.Node() for node in self.nodes}


        for link in self.links:
            self(link.node_a).linked_nodes.append(link.node_b)
            self(link.node_b).linked_nodes.append(link.node_a)

            self(link.node_a).links.append(link)
            self(link.node_b).links.append(link)

        for node in self.nodes:
            gn=self(node)
            gn.is_end=len(gn.linked_nodes)==1
            gn.is_beam=len(gn.linked_nodes)==2
            gn.is_joint=len(gn.linked_nodes)>2


    def width_iterator(self,start_nodes):



        state=self.IteratorState()

        if hasattr(start_nodes,"__iter__"):last_nodes=start_nodes
        else:last_nodes=[start_nodes]


        while len(last_nodes)!=0:


            next_nodes=[]
            for node in last_nodes :self(node).is_used=True
            for node in last_nodes:
                for linked_node,link in zip(self(node).linked_nodes,self(node).links):
                    if not self(linked_node).is_used:
                        if linked_node not in next_nodes :next_nodes.append(linked_node)

                        state.tail=node
                        state.head=linked_node
                        state.link=link
                        yield state


            last_nodes=next_nodes


    def forward_iterator(self,start_node):
        state=Graph.IteratorState()


        last_nodes=[start_node]
        while len(last_nodes)!=0:
            next_nodes=[]
            for node in last_nodes:
                for linked_node,link in zip(self(node).linked_nodes,self(node).links):
                    if link.tail is node:
                        if linked_node not in next_nodes :next_nodes.append(linked_node)

                        state.tail=node
                        state.head=linked_node
                        state.link=link
                        yield state

            last_nodes=next_nodes

    def backward_iterator(self,start_node):
        state=Graph.IteratorState()


        last_nodes=[start_node]
        while len(last_nodes)!=0:
            next_nodes=[]
            for node in last_nodes:
                for linked_node,link in zip(self(node).linked_nodes,self(node).links):
                    if link.head is node:
                        if linked_node not in next_nodes :next_nodes.append(linked_node)

                        state.tail=node
                        state.head=linked_node
                        state.link=link
                        yield state

            last_nodes=next_nodes

class Chain:
    class Node:
        def __init__(self,source,parent_name):
            self.children=[]
            self.parent=getattr(source,parent_name)

            self.is_end=False
            self.is_root=False


    def __init__(self,nodes,parent_name="parent"):
        self.parent_name=parent_name
        self.nodes=list(nodes)
        self.extenders={node:self.Node(node,parent_name) for node in self.nodes}

        self.roots=[]
        self.ends=[]

        for node in self.nodes:
            parent=self(node).parent
            if parent is not None:
                self(parent).children.append(node)

        for node in self.nodes:
            cn=self(node)
            if cn.parent is None:
                cn.is_root=True
                self.roots.append(node)
            if  len(cn.children)==0:
                cn.is_end=True
                self.ends.append(node)



    def __call__(self,item):
        if item in self.extenders:return self.extenders[item]
        else:return None

    def forward_iterator(self):
        last_nodes=list(self.roots)

        while len(last_nodes)!=0:
            next_nodes=[]
            for last_node in last_nodes:
                yield last_node
                next_nodes.extend(self(last_node).children)
            last_nodes=next_nodes





def methods(item):
    print()
    for name in dir(item):
        print(name)

def item_from_constraint(holder,name):
    if name not in holder.constraints:
        return None

    constraint=holder.constraints[name]

    if constraint.target is None:
        return None

    if type(constraint.target.data)==bpy.types.Armature and constraint.subtarget!="":
        return constraint.target.pose.bones[constraint.subtarget]
    else:
        return constraint.target
def item_to_constraint(holder,name,item):
    if name in holder.constraints:
        constraint=holder.constraints[name]
    else:
        constraint=holder.constraints.new(type='CHILD_OF')
        constraint.name=name
        constraint.mute=True

    if type(item)==bpy.types.PoseBone:
        constraint.target,constraint.subtarget=item.id_data,item.name
    else:
        constraint.target=item

def flatten(m):
    out=()
    for col in m.col:
        out+=tuple(col)
    return out


def compose_matrix(transform):
    l,r,s=transform

    if type(r)==mathutils.Quaternion:
        rotation_matrix=mathutils.Matrix.Rotation(r.angle,4,r.axis)
    elif type(r)==tuple:
        rotation_matrix=mathutils.Matrix.Rotation(r[3],4,(r[0],r[1],r[2]))
    else:
        rotation_matrix=r.to_matrix().to_4x4()

    matrix=mathutils.Matrix.Identity(3)
    matrix[0][0],matrix[1][1],matrix[2][2]=s

    matrix.rotate(r)

    matrix=matrix.to_4x4()
    matrix.col[3]=l[0],l[1],l[2],1

    return matrix

def matrix_world(item):
    if type(item) is bpy.types.PoseBone:
        return item.id_data.matrix_world@item.matrix
    if type(item) is bpy.types.Object:
        return item.matrix_world

def matrix_basis(item):
    if type(item) is bpy.types.PoseBone:
        return item.id_data.matrix_basis@item.bone.matrix_local@item.matrix_basis
    if type(item) is bpy.types.Object:
        return item.matrix_basis


def make_new_action(items):
    for item in items:
        if item.animation_data is not None:
            if item.animation_data.action is not None:
                item.animation_data.action=bpy.data.actions.new(item.animation_data.action.name+"_baked")

def get_trajectories(bake_items,custom_range=None):
    pass  #print("GET TRAJECTORIES")
    last_frame=bpy.context.scene.frame_current

    min_frame=max_frame=0
    frame_ranges=[]
    for bake_item in bake_items:
        if type(bake_item)==bpy.types.PoseBone:
            action_holder=bake_item.id_data
        if type(bake_item)==bpy.types.Object:
            action_holder=bake_item
        frame_range=range(0,0)
        if action_holder.animation_data is not None:
            if action_holder.animation_data.action is not None:
                r=action_holder.animation_data.action.frame_range
                frame_range=range(math.floor(r[0]),math.ceil(r[1])+1)

                if custom_range is not None:
                    frame_range=range(max(frame_range.start,custom_range.start),
                                      min(frame_range.stop,custom_range.stop+1))

        frame_ranges.append(frame_range)

        min_frame=min(min_frame,frame_range.start)
        max_frame=max(max_frame,frame_range.stop)

    max_frame_range=range(min_frame,max_frame)

    # trajectories=[[] for item in bake_items]
    trajectories={}
    for item in bake_items:
        trajectories[item]=[]
    for f in max_frame_range:

        bpy.context.scene.frame_set(f)
        bpy.context.view_layer.update()

        for item,frame_range in zip(bake_items,frame_ranges):
            if f in frame_range:
                if type(item)==bpy.types.PoseBone:
                    # m=item.id_data.convert_space(pose_bone=item,matrix=item.matrix,from_space='POSE',to_space='WORLD')
                    m=item.id_data.matrix_world@item.matrix
                    trajectories[item].append((m,f))
                if type(item)==bpy.types.Object:
                    trajectories[item].append((item.matrix_world.copy(),f))

    bpy.context.scene.frame_set(last_frame)
    return trajectories

def apply_trajectories(bake_items,trajectories,parents=None,apply_local=False,key_local=False):
    pass  #print("APPLY TRAJECTORIES")
    options={'INSERTKEY_NEEDED'}
    # options=set()
    # options = {'INSERTKEY_VISUAL'}

    print(trajectories.keys())
    for key in trajectories.keys():
        print(key.name,id(key))
    print("AAA")
    for x in range(len(bake_items)):

        item=bake_items[x]
        parent=None
        parent_trajectory=None
        if parents is not None:
            parent=parents[x]
            if parent is not None:
                if parent in trajectories:
                    parent_trajectory=trajectories[parent]
                else:
                    parent=None

                # print(parent.name,id(parent))
                # parent_trajectory=trajectories[parent]
        trajectory=trajectories[item]

        euler_prev=None
        local_euler_prev=None
        for y in range(len(trajectory)):
            matrix,f=trajectory[y]
            if parent is not None:
                parent_matrix,parent_f=parent_trajectory[y]

            if type(item)==bpy.types.PoseBone:
                if parent is not None and apply_local:
                    item.matrix_basis=(parent_matrix@(parent.bone.matrix_local.inverted()@item.bone.matrix_local)).inverted()@matrix

                else:
                    item.matrix_basis=(item.id_data.matrix_basis@item.bone.matrix_local).inverted()@matrix
                # item.matrix_basis=(item.id_data.matrix_basis@item.bone.matrix_local).inverted()@matrix

            if type(item)==bpy.types.Object:
                item.matrix_basis=matrix.copy()

            item.keyframe_insert("location",-1,f,item.name,options)

            rotation_mode=item.rotation_mode
            if rotation_mode=='QUATERNION':
                item.keyframe_insert("rotation_quaternion",-1,f,item.name,options)
            elif rotation_mode=='AXIS_ANGLE':
                item.keyframe_insert("rotation_axis_angle",-1,f,item.name,options)
            else:  # euler, XYZ, ZXY etc
                if euler_prev is not None:
                    euler=item.rotation_euler.copy()
                    euler.make_compatible(euler_prev)
                    item.rotation_euler=euler
                    euler_prev=euler
                    del euler
                else:
                    euler_prev=item.rotation_euler.copy()
                item.keyframe_insert("rotation_euler",-1,f,item.name,options)

            if key_local and parent is not None:
                local_quaternion=parent_matrix.to_quaternion().inverted()@matrix.to_quaternion()
                if rotation_mode=='QUATERNION':
                    item.free_ik_local_quaternion=local_quaternion
                    item.keyframe_insert("free_ik_local_quaternion",-1,f,item.name,options)
                elif rotation_mode=='AXIS_ANGLE':
                    axis=local_quaternion.axis
                    item.free_ik_local_axis_angle=local_quaternion.angle,axis[0],axis[1],axis[2]
                    item.keyframe_insert("free_ik_local_axis_angle",-1,f,item.name,options)
                else:  # euler, XYZ, ZXY etc
                    local_euler=mathutils.Euler((0,0,0),rotation_mode)

                    local_euler.rotate(local_quaternion)
                    # local_euler=local_quaternion.to_euler()
                    if local_euler_prev is not None:
                        local_euler.make_compatible(local_euler_prev)
                    local_euler_prev=local_euler.copy()

                    item.free_ik_local_euler=local_euler
                    item.keyframe_insert("free_ik_local_euler",-1,f,item.name,options)

            item.keyframe_insert("scale",-1,f,item.name,options)





def getattr_nested(source,path):
    splitted=path.split(".")

    out_source=source
    for name in splitted[:-1]:
        if hasattr(out_source,name):
            out_source=getattr(out_source,name)
        else:
            return None
    if hasattr(out_source,splitted[-1]):
        return getattr(out_source,splitted[-1])

def setattr_nested(source,path,value):
    splitted=path.split(".")

    out_source=source
    for name in splitted[:-1]:
        if hasattr(out_source,name):
            out_source=getattr(out_source,name)
        else:
            return None
    if hasattr(out_source,splitted[-1]):
        setattr(out_source,splitted[-1],value)

def make_keys():
    # print("MAKE KEYS")

    gv.keymaps={
        '3D View':(
            ({"idname":"transform.translate","properties.texture_space":False,"properties.gpencil_strokes":False},
             {"idname":Operators.TransformReplacer.bl_idname,"properties.mode":'TRANSLATE'}),
            ({"idname":"transform.rotate","properties.gpencil_strokes":False},
             {"idname":Operators.TransformReplacer.bl_idname,"properties.mode":'ROTATE'}),
            ({"idname":"transform.resize","properties.texture_space":False,"properties.gpencil_strokes":False},
             {"idname":Operators.TransformReplacer.bl_idname,"properties.mode":'RESIZE'}),
            ({"idname":"transform.mirror","properties.gpencil_strokes":False},
             {"idname":Operators.TransformReplacer.bl_idname,"properties.mode":'MIRROR'}),

        ),

        'Pose':(

            ({"idname":"pose.paste","properties.flipped":False},{"idname":Operators.PastePoseReplacer.bl_idname,"properties.flipped":False}),
            ({"idname":"pose.paste","properties.flipped":True},{"idname":Operators.PastePoseReplacer.bl_idname,"properties.flipped":True}),


        ),

        '3D View Tool: Pose, Breakdowner':(

            ({"idname":"pose.breakdown"},{"idname":Operators.TransformReplacer.bl_idname,"properties.mode":'BREAKDOWN'}),

        ),

        'Generic Gizmo Maybe Drag':(

            (
            {"idname":"gizmogroup.gizmo_tweak"},{"idname":Operators.TransformReplacer.bl_idname,"properties.mode":'GIZMO'}),

        ),


    }

    if bpy.context.window_manager.keyconfigs.active is None:
        gv.keyconfig=bpy.context.window_manager.keyconfigs.active
    else:
        gv.keyconfig=bpy.context.window_manager.keyconfigs.default

    to_create=[]
    for keymap in gv.keymaps:
        keymap_items=gv.keyconfig.keymaps[keymap].keymap_items
        for keymap_item in keymap_items:
            for to_replace_settings,replacer_settings in gv.keymaps[keymap]:
                match=True
                for key,value in to_replace_settings.items():
                    if getattr_nested(keymap_item,key)!=value:
                        match=False
                        break
                if match:
                    to_create.append((keymap_items,keymap_item,replacer_settings))

    for keymap_items,keymap_item,replacer_settings in to_create:
        # print(keymap_items,keymap_item,replacer_settings)
        item=keymap_items.new_from_item(keymap_item,head=True)

        for key,value in replacer_settings.items():
            setattr_nested(item,key,value)

def clear_keys():
    # print("CLEAR KEYS")

    to_delete=[]
    if gv.keymaps is not None:
        for keymap in gv.keymaps:
            keymap_items=gv.keyconfig.keymaps[keymap].keymap_items
            for keymap_item in keymap_items:
                for to_replace_settings,replacer_settings in gv.keymaps[keymap]:
                    match=True
                    for key,value in replacer_settings.items():
                        if getattr_nested(keymap_item,key)!=value:
                            match=False
                            break
                    if match:
                        to_delete.append((keymap_items,keymap_item))

        for keymap_items,keymap_item in to_delete:
            keymap_items.remove(keymap_item)



def main_handler():

    gvs=gv.structure

    item_set=gvs.get_item_set()
    if item_set!=gvs.last.item_set:
        gvs.make()
        gvs.last.item_set=item_set



    if gv.operator.modal_transform_start:
        print("TRANSFORM START")
        for node in gvs.nodes:
            node.in_transform=node.get_transform()
            node.out_transform=node.get_transform()
            node.reference_transform=node.get_transform()
            node.modal_start_transform=node.get_transform()

            node.matrix=node.get_matrix()
            node.in_matrix=node.get_matrix()
            node.out_matrix=node.get_matrix()
            node.modal_start_matrix=node.get_matrix()

            node.last=gvs.Node.Last(node)
            node.change=gvs.Node.Change()




    if gv.operator.modal_transform_cancel:
        print("TRANSFORM CANCEL")
        for node in gvs.nodes:
            node.apply_transform(node.modal_start_transform)

    if gv.operator.modal_transform_confirm:
        print("TRANSFORM CONFIRM")

        # for node in gvs.nodes:
        #     node.apply_matrix(node.matrix)
        #
        #     node.in_transform=node.get_transform()
        #     node.out_transform=node.get_transform()
        #     node.reference_transform=node.get_transform()
        #     node.modal_start_transform=node.get_transform()
        #
        #     node.matrix=node.get_matrix()
        #     node.in_matrix=node.get_matrix()
        #     node.out_matrix=node.get_matrix()
        #     node.modal_start_matrix=node.get_matrix()
        #     node.last=gvs.Node.Last(node)
        #     node.change=gvs.Node.Change()

    if gv.operator.modal_transform_cancel or gv.operator.modal_transform_confirm or not gv.operator.is_modal_transform and not gv.frame:
        for node in gvs.nodes:
            node.frame_parent=node.get_frame_parent()
            node.matrix=node.get_matrix()
            node.frame_parent_matrix=node.get_frame_parent_matrix()
        chain=Chain(gvs.nodes,"frame_parent")

        for node in chain.forward_iterator():
            parent=chain(node).parent
            if parent is None:
                local_matrix=compose_matrix(node.get_local_transform())
            else:
                local_matrix=(parent.matrix@node.frame_parent_matrix).inverted()@node.matrix

            reference=node.get_local_transform()

            local_transform=gvs.Node.Transform()
            local_transform.from_matrix(local_matrix,reference)

            node.apply_local_transform(local_transform)




    if gv.scene:
        if gv.before:

            print("SCENE BEFORE",random.randint(0,100))
            gv.operator.update()



            def make_paths(item,paths):


                if type(item)==bpy.types.PoseBone:
                    base_path='pose.bones["{}"].'.format(item.name)
                    fcurve_holder=item.id_data
                    group_name=item.name
                else:
                    base_path=""
                    fcurve_holder=item
                    group_name="Object Transforms"

                if item.rotation_mode=='QUATERNION':rotation_name="quaternion"
                elif item.rotation_mode=='AXIS_ANGLE':rotation_name="axis_angle"
                else:rotation_name="euler"



                if bpy.context.scene.compact_keyframe_insert_menu.use_delta and bpy.context.mode=='OBJECT':
                    location_path="delta_location"
                    rotation_path="delta_"+rotation_name
                    scale_name="delta_scale"
                else:
                    location_path="location"
                    rotation_path="rotation_"+rotation_name
                    scale_name="scale"

                kwargs={"group_method":'NAMED',"group_name":group_name}

                if bpy.context.scene.compact_keyframe_insert_menu.use_location:
                    args=fcurve_holder,base_path+location_path
                    paths.append((args,kwargs))

                if bpy.context.scene.compact_keyframe_insert_menu.use_rotation and not (bpy.context.scene.compact_keyframe_insert_menu.use_delta and bpy.context.mode=='OBJECT' and item.rotation_mode=='AXIS_ANGLE'):
                    args=fcurve_holder,base_path+rotation_path
                    paths.append((args,kwargs))

                if bpy.context.scene.compact_keyframe_insert_menu.use_scale:
                    args=fcurve_holder,base_path+scale_name
                    paths.append((args,kwargs))


                if bpy.context.scene.free_ik.use_local_location:
                    args=fcurve_holder,base_path+"free_ik_local_location"
                    paths.append((args,kwargs))
                if bpy.context.scene.free_ik.use_local_rotation:
                    args=fcurve_holder,base_path+"free_ik_local_"+rotation_name
                    paths.append((args,kwargs))
                if bpy.context.scene.free_ik.use_local_scale:
                    args=fcurve_holder,base_path+"free_ik_local_scale"
                    paths.append((args,kwargs))


            if "Transform" in bpy.context.scene.keying_sets_all:
                paths=[]
                for node in gvs.nodes:
                    node.is_selected=node.get_is_selected()
                    if node.is_selected:
                        make_paths(node.source,paths)
                bpy.context.scene.keying_sets_all["Transform"].type_info.external_paths.clear()
                bpy.context.scene.keying_sets_all["Transform"].type_info.external_paths.extend(paths)





        if gv.after:
            print("SCENE AFTER")

            if not gv.operator.is_modal_transform:return


            for node in gvs.nodes:
                node.pose_parent=node.get_pose_parent()
                node.is_selected=node.get_is_selected()
                node.in_matrix=node.get_matrix()
                node.in_transform=node.get_transform()

                out_in_change=gvs.Node.Change.Transform()
                in_in_change=gvs.Node.Change.Transform()
                change=gvs.Node.Change.Transform()

                node.last.out_transform.compare(node.in_transform,out_in_change)
                node.last.in_transform.compare(node.in_transform,in_in_change)

                if out_in_change.location:
                    change.location=in_in_change.location
                if out_in_change.rotation:
                    change.rotation=in_in_change.rotation
                if out_in_change.scale:
                    change.scale=in_in_change.scale

                cl,cr,cs=node.in_matrix.decompose()
                ul,ur,us=node.modal_start_matrix.decompose()

                l,r,s=ul,ur,us

                if change.location: l=cl
                if change.rotation: r=cr
                if change.scale: s=cs

                node.in_matrix=compose_matrix((l,r,s))

                node.transform_change=out_in_change.any
                node.rest_matrix=node.last.out_matrix
                node.matrix=node.in_matrix@node.last.in_matrix.inverted()@node.last.out_matrix

                node.change.final=change

                node.change.modal_transform=gvs.Node.Change.Transform()
                node.modal_start_transform.compare(node.in_transform,node.change.modal_transform)

                if node.is_selected:
                    c=gvs.Node.Change.Transform()
                    node.modal_start_transform.compare(node.in_transform,c)
                    print(c)
                    # print(out_in_change)
                    # print(in_in_change)


            chain=Chain(gvs.nodes,"pose_parent")

            def apply_parent(node):

                inherit_location=True
                inherit_rotation=True
                inherit_scale=True

                individual_origins=True

                parent=chain(node).parent
                root=node.root

                if root is not None:
                    root_rest_location,root_rest_rotation,root_rest_scale=root.rest_matrix.decompose()
                    root_location,root_rotation,root_scale=root.matrix.decompose()

                    parent_rest_location,parent_rest_rotation,parent_rest_scale=parent.rest_matrix.decompose()
                    parent_location,parent_rotation,parent_scale=parent.matrix.decompose()

                    rest_location,rest_rotation,rest_scale=node.rest_matrix.decompose()
                    location,rotation,scale=node.matrix.decompose()

                    if inherit_location:
                        # location=root.matrix@root.rest_matrix.inverted()@rest_location
                        location=parent.matrix@parent.rest_matrix.inverted()@rest_location

                    if inherit_rotation :
                        if node.change.final.rotation:
                            rotation=parent_rotation@parent_rest_rotation.inverted()@rotation
                        else:
                            rotation=parent_rotation@parent_rest_rotation.inverted()@rest_rotation

                    # rest_rotation.inverted()@rest_rotation
                    # parent.rotation@parent_rest_rotation.inverted()@

                    if inherit_scale:
                        if 0 not in root_rest_scale:
                            scale=mathutils.Vector(rest_scale[x]*root_scale[x]/root_rest_scale[x] for x in range(3))

                    node.matrix=compose_matrix((location,rotation,scale))

                    # print(root.rest_matrix.inverted()@rest_location)
                    # print(root.matrix)

                    # node.matrix=root.matrix@(root.rest_matrix.inverted()@node.rest_matrix)



            for node in gvs.nodes:node.root=None

            for node in chain.forward_iterator():
                parent=chain(node).parent
                child=node
                if parent is not None:
                    if parent.root is None:
                        if parent.transform_change:
                            child.root=parent
                    else:
                        child.root=parent.root

            for node in gvs.nodes:
                print(node,node.root)




            do_transform=any(node.transform_change for node in gvs.nodes)
            print(do_transform)

            if do_transform:
                for node in chain.forward_iterator():
                    apply_parent(node)

                for node in gvs.nodes:
                    node.apply_matrix(node.matrix)



                for node in gvs.nodes:
                    node.last.in_transform=node.in_transform
                    node.last.out_transform=node.get_transform()
                    node.last.in_matrix=node.in_matrix
                    node.last.out_matrix=node.matrix





    if gv.frame:
        if gv.after:
            print("FRAME AFTER")
            for node in gvs.nodes:
                node.is_pinned=node.get_is_pinned()
                node.is_constrained=node.get_is_constrained()
                node.frame_parent=node.get_frame_parent()
                node.transform=node.get_transform()
                node.local_transform=node.get_local_transform()
                node.matrix=node.get_matrix()
                node.frame_parent_matrix=node.get_frame_parent_matrix()





            chain=Chain(gvs.nodes,"frame_parent")
            for node in chain.forward_iterator():
                if not (node.is_pinned or node.is_constrained):
                    parent=chain(node).parent
                    if parent is not None:
                        node.matrix=parent.matrix@node.frame_parent_matrix@compose_matrix(node.local_transform)
                        print(node,node.frame_parent)
                    node.apply_matrix(node.matrix)






class Settings:
    class FreeIKNodeSettings(bpy.types.PropertyGroup):
        node_a_name=gv.names.prime_name+"_node_a"
        node_b_name=gv.names.prime_name+"_node_b"

        def get_node_a(self): return item_from_constraint(self.id_data,self.node_a_name)

        def set_node_a(self,other): item_to_constraint(self.id_data,self.node_a_name,other)

        node_a=property(get_node_a,set_node_a)

        def get_node_b(self): return item_from_constraint(self.id_data,self.node_b_name)

        def set_node_b(self,other): item_to_constraint(self.id_data,self.node_b_name,other)

        node_b=property(get_node_b,set_node_b)



        local_matrix_a: bpy.props.FloatVectorProperty(name="Local matrix",subtype='MATRIX',size=16)
        local_matrix_b: bpy.props.FloatVectorProperty(name="Local matrix",subtype='MATRIX',size=16)

        id:bpy.props.FloatProperty(name="ID",default=-1)

        priority: bpy.props.IntProperty(name="Priority",default=0)

        type:bpy.props.EnumProperty(name="Type",default='NONE',
                               items=[
                                   ('NONE',"None","None",'NONE',0),
                                   ('NODE',"Node","Node",'NONE',1),
                                   ('LINK',"Link","Link",'NONE',2),
                               ]
                               )




    class FreeIKSceneSettings(bpy.types.PropertyGroup):
        collection: bpy.props.PointerProperty(type=bpy.types.Collection)

        enable_solver: bpy.props.BoolProperty(name="Enable solver",default=True)

        scene_iterations: bpy.props.IntProperty(name="Posing iterations",default=20)
        frame_iterations: bpy.props.IntProperty(name="Playback iterations",default=5)

        inherit_location: bpy.props.BoolProperty(name="Inherit location",default=True)
        inherit_rotation: bpy.props.BoolProperty(name="Inherit rotation",default=True)
        inherit_scale: bpy.props.BoolProperty(name="Inherit scale",default=True)

        show_generic: bpy.props.BoolProperty(name="Generic",default=True)
        show_pinned: bpy.props.BoolProperty(name="Pinned",default=True)

        solver_mode: bpy.props.EnumProperty(name="Posing mode",
                                            items=[
                                                (gv.names.smooth,"","",'NONE',0),
                                                (gv.names.rope,"","",'NONE',1),
                                                (gv.names.stretch,"","",'NONE',2),

                                            ]
                                            )
        stretch_mode: bpy.props.EnumProperty(name="Stretch mode",
                                             items=[
                                                 (gv.names.stretch_both,"","",'NONE',0),
                                                 (gv.names.stretch_head,"","",'NONE',1),
                                                 (gv.names.stretch_tail,"","",'NONE',2),
                                             ]
                                             )

        use_local_location: bpy.props.BoolProperty(name="Local location",description="Insert keyframes on local location of FreeIK nodes",default=True)
        use_local_rotation: bpy.props.BoolProperty(name="Local rotation",description="Insert keyframes on local rotation of FreeIK nodes",default=True)
        use_local_scale: bpy.props.BoolProperty(name="Local scale",description="Insert keyframes on local scale of FreeIK nodes",default=True)



        use_linked: bpy.props.BoolProperty(name="Linked",description="Insert keyframes on FreeIK nodes which are connected to selected",default=True)


class Panels:
    # class FreeIKConstraintPanel(bpy.types.Panel):
    #     bl_idname="OBJECT_PT_free_ik_constraint_panel"
    #     bl_label="FreeIK constraint"
    #     bl_space_type='PROPERTIES'
    #     bl_region_type='WINDOW'
    #     bl_context="object"
    #
    #     @classmethod
    #     def poll(self,context):
    #         return True
    #
    #     def draw(self,context):
    #         layout=self.layout
    #         layout.prop(context.object.free_ik,"is_enabled")
    #         layout.prop(context.object.free_ik,"is_rig_enabled")

    class FreeIKBonePanel(bpy.types.Panel):
        bl_idname="BONE_PT_free_ik_bone_panel"
        bl_label="FreeIK constraint"
        bl_space_type='PROPERTIES'
        bl_region_type='WINDOW'
        bl_context="bone"

        @classmethod
        def poll(self,context):
            return context.active_pose_bone is not None

        def draw(self,context):
            layout=self.layout
            layout.prop(context.active_pose_bone,"free_ik_is_enabled")

    class FreeIKObjectPanel(bpy.types.Panel):
        bl_idname="OBJECT_PT_free_ik_object_panel"
        bl_label="FreeIK constraint"
        bl_space_type='PROPERTIES'
        bl_region_type='WINDOW'
        bl_context="object"

        @classmethod
        def poll(self,context):
            return True

        def draw(self,context):
            layout=self.layout
            layout.prop(context.object,"free_ik_is_enabled")

class Operators:
    class FREEIK_MT_FreeIKPieMenu(bpy.types.Menu):
        """I am help string"""
        bl_label="FreeIK pie menu"

        # bl_space_type='VIEW_3D'
        # bl_region_type='WINDOW'

        def draw(self,context):
            layout=self.layout
            pie=layout.menu_pie()

            #LEFT
            column=pie.column(align=True)
            column.scale_x=1.5
            column.scale_y=1.5
            column.operator(Operators.MakeLinks.bl_idname,text="Make links",icon='POSE_HLT')


            #RIGHT

            column=pie.column(align=True)
            column.scale_x=1.5
            column.scale_y=1.5
            column.operator(Operators.SetParent.bl_idname,text="Pose parent",icon='MESH_CAPSULE').mode='POSE'
            column.operator(Operators.ClearParent.bl_idname,text="Unparent",icon='PANEL_CLOSE').mode='POSE'
            # column.popover("FREEIK_PT_FreeIKParentingMenu")

            column.separator()


            row=column.row(align=True)
            # row.scale_x=row.scale_y=1.5

            column=row.column(align=True)
            column.scale_x=1.0
            column.operator(Operators.SetParent.bl_idname,text="Playback parent",icon='MESH_CAPSULE').mode='FRAME'
            column.operator(Operators.ClearParent.bl_idname,text="Unparent",icon='PANEL_CLOSE').mode='FRAME'

            column=row.column(align=True)
            column.scale_y=2
            column.scale_x=1
            row=column.row(align=True)
            operator=row.operator(Operators.Keyframer.bl_idname,text="",text_ctxt="Isert keyframe",icon='KEY_HLT')
            operator.mode='INSERT'
            operator.target='FRAME_PARENT'

            operator=row.operator(Operators.Keyframer.bl_idname,text="",text_ctxt="Delete keyframe",icon='KEY_DEHLT')
            operator.mode='DELETE'
            operator.target='FRAME_PARENT'

            operator=row.operator(Operators.Keyframer.bl_idname,text="",text_ctxt="Clear keyframes",icon='X')
            operator.mode='CLEAR'
            operator.target='FRAME_PARENT'


            #BOTTOM

            main_column=pie.column(align=True)
            main_column.scale_x=main_column.scale_y=1.5

            row=main_column.row(align=True)
            # row.scale_x=row.scale_y=1.5

            column=row.column(align=True)
            column.scale_x=1.0
            column.operator(Operators.SetLinkState.bl_idname,text="Enable link",icon='LINKED').link_state=True
            column.operator(Operators.SetLinkState.bl_idname,text="Disable link",icon='UNLINKED').link_state=False

            column=row.column(align=True)
            column.scale_y=2
            column.scale_x=1
            row=column.row(align=True)
            operator=row.operator(Operators.Keyframer.bl_idname,text="",text_ctxt="Isert keyframe",icon='KEY_HLT')
            operator.mode='INSERT'
            operator.target='IS_ENABLED'

            operator=row.operator(Operators.Keyframer.bl_idname,text="",text_ctxt="Delete keyframe",icon='KEY_DEHLT')
            operator.mode='DELETE'
            operator.target='IS_ENABLED'

            operator=row.operator(Operators.Keyframer.bl_idname,text="",text_ctxt="Clear keyframes",icon='X')
            operator.mode='CLEAR'
            operator.target='IS_ENABLED'

            # column=row.column(align=True)

            main_column.separator()
            main_column.operator(Operators.SetLimits.bl_idname,text="Set limits",icon='DRIVER_ROTATIONAL_DIFFERENCE').mode='START'





            #TOP

            row=pie.row(align=True)
            row.scale_x=row.scale_y=1.5

            column=row.column(align=True)
            column.scale_x=1.0
            column.operator(Operators.SetPinState.bl_idname,text="Pin",icon='PINNED').pin_state=True
            column.operator(Operators.SetPinState.bl_idname,text="Unpin",icon='UNPINNED').pin_state=False

            column=row.column(align=True)
            column.scale_y=2
            column.scale_x=1
            row=column.row(align=True)
            operator=row.operator(Operators.Keyframer.bl_idname,text="",text_ctxt="Isert keyframe",icon='KEY_HLT')
            operator.mode='INSERT'
            operator.target='IS_PINNED'

            operator=row.operator(Operators.Keyframer.bl_idname,text="",text_ctxt="Delete keyframe",icon='KEY_DEHLT')
            operator.mode='DELETE'
            operator.target='IS_PINNED'

            operator=row.operator(Operators.Keyframer.bl_idname,text="",text_ctxt="Clear keyframes",icon='X')
            operator.mode='CLEAR'
            operator.target='IS_PINNED'






        def draw_x(self,context):
            layout=self.layout
            pie=layout.menu_pie()

            column=pie.column(align=True)
            column.scale_x=1.5
            column.scale_y=1.5
            column.operator(MakeLinks.bl_idname,text="Make links",icon='POSE_HLT')

            row=column.row(align=True)
            split=row.split(align=True,factor=0.5)

            split.operator(Bake.bl_idname,text="Bake",icon='ACTION')
            split.operator(SetRigState.bl_idname,text="",icon='OUTLINER_OB_LIGHT').rig_state=True
            split.operator(SetRigState.bl_idname,text="",icon='LIGHT').rig_state=False

            column.operator(ClearLinks.bl_idname,text="Clear links",icon='PANEL_CLOSE')

            column=pie.column(align=True)
            column.scale_x=1.5
            column.scale_y=1.5
            column.operator(SetParent.bl_idname,text="Set pose parent",icon='MESH_CAPSULE').mode='POSE'
            column.operator(ClearParent.bl_idname,text="Clear pose parent",icon='PANEL_CLOSE').mode='POSE'
            column.popover("FREEIK_PT_FreeIKParentingMenu")

            row=pie.row(align=True)
            row.scale_x=row.scale_y=1.5

            column=row.column(align=True)
            column.scale_x=1.0
            column.operator(SetLinkState.bl_idname,text="Enable link",icon='LINKED').link_state=True
            column.operator(SetLinkState.bl_idname,text="Disable link",icon='UNLINKED').link_state=False

            column=row.column(align=True)
            column.scale_y=2
            column.scale_x=1
            row=column.row(align=True)
            operator=row.operator(Keyframer.bl_idname,text="",text_ctxt="Isert keyframe",icon='KEY_HLT')
            operator.mode='INSERT'
            operator.target='IS_ENABLED'

            operator=row.operator(Keyframer.bl_idname,text="",text_ctxt="Delete keyframe",icon='KEY_DEHLT')
            operator.mode='DELETE'
            operator.target='IS_ENABLED'

            operator=row.operator(Keyframer.bl_idname,text="",text_ctxt="Clear keyframes",icon='X')
            operator.mode='CLEAR'
            operator.target='IS_ENABLED'

            row=pie.row(align=True)
            row.scale_x=row.scale_y=1.5

            column=row.column(align=True)
            column.scale_x=1.0
            column.operator(SetPinState.bl_idname,text="Pin",icon='PINNED').pin_state=True
            column.operator(SetPinState.bl_idname,text="Unpin",icon='UNPINNED').pin_state=False

            column=row.column(align=True)
            column.scale_y=2
            column.scale_x=1
            row=column.row(align=True)
            operator=row.operator(Keyframer.bl_idname,text="",text_ctxt="Isert keyframe",icon='KEY_HLT')
            operator.mode='INSERT'
            operator.target='IS_PINNED'

            operator=row.operator(Keyframer.bl_idname,text="",text_ctxt="Delete keyframe",icon='KEY_DEHLT')
            operator.mode='DELETE'
            operator.target='IS_PINNED'

            operator=row.operator(Keyframer.bl_idname,text="",text_ctxt="Clear keyframes",icon='X')
            operator.mode='CLEAR'
            operator.target='IS_PINNED'

    class FreeIKPieMenuStarter(bpy.types.Operator):
        bl_idname="free_ik.pie_menu_start"
        bl_label="FreeIK tools pie menu"

        @classmethod
        def poll(self,context):
            return context.mode in ('OBJECT','POSE')

        def invoke(self,context,event):
            bpy.ops.wm.call_menu_pie(name="FREEIK_MT_FreeIKPieMenu")
            return {'FINISHED'}

    class MakeLinks(bpy.types.Operator):
        """Make links"""
        bl_idname="free_ik.make_links"
        bl_label="Make links"
        # bl_options = {'REGISTER', 'UNDO','USE_EVAL_DATA'}
        bl_options={'REGISTER','UNDO'}

        mode: bpy.props.EnumProperty(name="Mode",
                                     items=[
                                         ('ARMATURE',"Armature","Create links from armature",'NONE',0),
                                         ('CURSOR',"Two objects","Link two objects",'NONE',1),
                                     ]
                                     )
        use_selected_bones_only: bpy.props.BoolProperty(name="Use selected bones only",default=False)

        adapt_animation: bpy.props.BoolProperty(name="Adapt animation",default=False)

        use_custom_range: bpy.props.BoolProperty(name="Use custom range",default=False)
        start_frame: bpy.props.IntProperty(name="Start frame",description="Start frame for baking",default=1,min=0)
        end_frame: bpy.props.IntProperty(name="End frame",description="End frame for baking",default=250,min=1)




        @classmethod
        def poll(self,context):
            return context.mode in ('OBJECT','POSE')

        def execute(self,context):

            def make_constraint(node_a,node_b,const_point=None):



                const_empty_name="const_empty"
                collection_name="constraint_collection"

                const_empty=bpy.data.objects.new(const_empty_name,None)

                const_empty.free_ik.node_a=node_a
                const_empty.free_ik.node_b=node_b

                for node in (node_a,node_b,const_empty):
                    if node.free_ik.id<0:
                        gv.structure.make_id(node)
                node_a.free_ik.type='NODE'
                node_b.free_ik.type='NODE'
                const_empty.free_ik.type='LINK'



                # const_empty.location.y-=node_b.bone.length

                # const_empty.matrix_basis=matrix_world(node_b)
                # if const_point is not None:const_empty.location=const_point



                if type(node_a) is bpy.types.PoseBone:
                    const_empty.matrix_basis=node_b.id_data.matrix_basis@node_b.bone.matrix_local@node_b.matrix_basis
                    if const_point is not None: const_empty.location=const_point


                    const_empty.parent=node_a.id_data
                    const_empty.parent_type='BONE'
                    const_empty.parent_bone=node_a.name
                    const_empty.matrix_parent_inverse=(node_a.id_data.matrix_basis@node_a.bone.matrix_local@node_a.matrix_basis@mathutils.Matrix.Translation((0,node_a.bone.length,0))).inverted()
                #
                #
                if type(node_a) is bpy.types.Object:
                    const_empty.matrix_basis=node_a.matrix_world.copy()
                    const_empty.matrix_basis.col[3][0:3]=context.scene.cursor.location

                    const_empty.parent=node_a
                    const_empty.parent_type='OBJECT'
                    const_empty.matrix_parent_inverse=node_a.matrix_world.inverted()



                if context.scene.free_ik.collection is None:
                    context.scene.free_ik.collection=bpy.data.collections.new(collection_name)
                    context.scene.collection.children.link(context.scene.free_ik.collection)

                context.scene.free_ik.collection.objects.link(const_empty)

                #SET LOCAL MATRICES

                const_empty.free_ik.local_matrix_a=flatten(matrix_world(node_a).inverted()@const_empty.matrix_basis)
                const_empty.free_ik.local_matrix_b=flatten(matrix_world(node_b).inverted()@const_empty.matrix_basis)



            if self.mode=='CURSOR':
                pass  #print("CURSOR")

                node_a=node_b=None

                if context.mode=='POSE':
                    node_a=context.active_pose_bone
                    selected_items=context.selected_pose_bones


                if context.mode=='OBJECT':
                    node_a=context.object
                    selected_items=context.selected_objects


                if len(selected_items)==2:
                    for item in selected_items:
                        if item is not node_a:
                            node_b=item
                            break

                if None not in (node_a,node_b):
                    make_constraint(node_a,node_b,context.scene.cursor.location)

            if self.mode=='ARMATURE':
                selected_bones=set()
                if context.mode=='OBJECT':
                    for selected_object in context.selected_objects:
                        if type(selected_object.data)==bpy.types.Armature:
                            for pose_bone in selected_object.pose.bones:
                                if not pose_bone.bone.hide:
                                    if pose_bone.bone.select or not self.use_selected_bones_only:
                                        if any([bl and al for bl,al in zip(pose_bone.bone.layers,selected_object.data.layers)]):
                                            selected_bones.add(pose_bone)

                if context.mode=='POSE':
                    if self.use_selected_bones_only:
                        selected_bones=set(context.selected_pose_bones)
                    else:
                        selected_bones=set(context.visible_pose_bones)


                work_bones=set()
                work_armatures=set()
                used_pairs=set()
                chain_pairs=[]
                for selected_bone in selected_bones:
                    if selected_bone.parent is not None:
                        selected_bone.bone.bbone_custom_handle_start=selected_bone.parent.bone
                        selected_bone.bone.bbone_handle_type_start='ABSOLUTE'
                    if len(selected_bone.children)==1:
                        selected_bone.bone.bbone_custom_handle_end=selected_bone.children[0].bone
                        selected_bone.bone.bbone_handle_type_end='ABSOLUTE'

                    if selected_bone.parent in selected_bones:

                        work_bones.add(selected_bone.parent)
                        work_bones.add(selected_bone)

                        work_armatures.add(selected_bone.id_data)

                        pair=selected_bone.parent,selected_bone

                        used_pairs.add(frozenset(pair))

                        chain_pairs.append(pair)




                class PointNode():
                    def __init__(self,bone,point):
                        self.point=point
                        self.bone=bone
                        self.is_used=False
                    def length(self,other):
                        return (self.point-other.point).length

                point_nodes=[]
                for selected_bone in selected_bones:
                    m=selected_bone.id_data.matrix_world
                    for p in (selected_bone.head,selected_bone.tail):
                        point_nodes.append(PointNode(selected_bone,m@p))


                threshold=0.1
                clusters=[]
                for node in point_nodes:
                    if not node.is_used:
                        node.is_used=True
                        cluster={node}
                        for other_node in point_nodes:
                            if not other_node.is_used:
                                if node.bone is not other_node.bone:
                                    if node.length(other_node)<=threshold:
                                        pair=frozenset((node.bone,other_node.bone))
                                        if pair not in used_pairs:
                                            other_node.is_used=True
                                            cluster.add(other_node)
                                            used_pairs.add(pair)

                        if len(cluster)>=2:
                            clusters.append(cluster)
                print(len(clusters))
                print()
                free_pairs=[]
                for cluster in clusters:
                    print(len(cluster))
                    start_node=next(iter(cluster))
                    for node in cluster:
                        if node is not start_node:

                            work_bones.add(start_node.bone)
                            work_bones.add(node.bone)

                            work_armatures.add(start_node.bone.id_data)
                            work_armatures.add(node.bone.id_data)

                            free_pairs.append((start_node.bone,node.bone,start_node.point))



                bake_items=sorted(list(work_bones),key=lambda v:-len(v.children_recursive))
                bake_parents=[]
                for item in bake_items:
                    if item.parent in work_bones:parent=item.parent
                    else:parent=None
                    bake_parents.append(parent)

                before_matrices=[matrix_world(item) for item in bake_items]

                if self.adapt_animation:
                    if self.use_custom_range:custom_range=range(self.start_frame,self.end_frame)
                    else:custom_range=None
                    trajectories=get_trajectories(bake_items,custom_range)



                if len(work_bones)!=0:
                    last_selected=context.selected_objects.copy()
                    last_active=context.object
                    last_mode=context.mode

                    for item in bpy.data.objects:
                        item.select_set(False)
                    for item in work_armatures:
                        item.select_set(True)

                    context.view_layer.objects.active=next(iter(work_armatures))

                    bpy.ops.object.mode_set(mode='EDIT')

                    for used_bone in work_bones:
                        used_bone.id_data.data.edit_bones[used_bone.name].parent=None

                    bpy.ops.object.mode_set(mode=last_mode)

                    for item in bpy.data.objects:
                        item.select_set(False)
                    for item in last_selected:
                        item.select_set(True)
                    context.view_layer.objects.active=last_active

                    context.view_layer.update()

                if self.adapt_animation:
                    make_new_action(work_armatures)
                    apply_trajectories(bake_items,trajectories,parents=bake_parents,apply_local=False,key_local=True)

                for item,matrix in zip(bake_items,before_matrices):
                    item.matrix_basis=(item.id_data.matrix_basis@item.bone.matrix_local).inverted()@matrix

                gv.structure.update_ids()

                for node_a,node_b in chain_pairs:
                    make_constraint(node_a,node_b)
                    item_to_constraint(node_b,gv.names.pose_parent_name,node_a)
                    node_b.free_ik_frame_parent_id=node_a.free_ik.id

                for node_a,node_b,point in free_pairs:
                    make_constraint(node_a,node_b,point)




            link_sources=[]
            node_sources=set()

            for link_source in bpy.context.scene.free_ik.collection.objects:
                node_sources.update((link_source.free_ik.node_a,link_source.free_ik.node_b))
                link_sources.append(link_source)

            node_sources=list(node_sources)

            link_sources.sort(key=lambda v:v.name)
            node_sources.sort(key=lambda v:v.name)

            for x,source in enumerate(itertools.chain(link_sources,node_sources)):
                source.free_ik.priority=x




            context.view_layer.update()


            return {'FINISHED'}

        def invoke(self,context,event):
            # context.scene.update()
            return context.window_manager.invoke_props_dialog(self)

        def check(self,context):
            return True

        def draw(self,context):

            layout=self.layout
            layout.prop(self,"mode")
            if self.mode=='ARMATURE':
                layout.prop(self,"use_selected_bones_only")
                layout.prop(self,"adapt_animation")
                if self.adapt_animation:
                    layout.prop(self,"use_custom_range")
                    if self.use_custom_range:
                        layout.prop(self,"start_frame")
                        layout.prop(self,"end_frame")

    class SetLimits(bpy.types.Operator):
        """Interactively set constraint limits"""
        bl_idname="free_ik.set_limits"
        bl_label="Set limits"
        bl_options={'REGISTER','UNDO'}

        mode: bpy.props.EnumProperty(name="Settings",options={'HIDDEN'},
                                     items=[
                                         ('START',"","",'NONE',0),
                                         ('MODE',"","",'NONE',1),
                                         ('CLEAR',"","",'NONE',2),
                                         ('HELP',"","",'NONE',3),
                                         ('STOP',"","",'NONE',4),

                                     ]
                                     )
        update_mode=0
        show_help=True

        class ConstraintHolder:
            def __init__(self,constraint):
                self.name=constraint.name

                self.is_valid=False

                self.constraint=None
                self.node_a=None
                self.node_b=None

                self.constraint_initial_matrix=None
                self.initial_matrix_a=None
                self.initial_matrix_b=None

                self.work_euler=None
                self.last_quaternion=None

                self.visual_matrix=None

                self.validate()

            def validate(self):
                self.node_a=None
                self.node_b=None
                if self.name in bpy.data.objects:
                    self.constraint=bpy.data.objects[self.name]
                    self.node_a=self.constraint.free_ik.node_a
                    self.node_b=self.constraint.free_ik.node_b

                if None in (self.node_a,self.node_b):
                    self.is_valid=False
                else:
                    self.is_valid=True

            def update_initial(self):
                self.constraint_initial_matrix=matrix_world(self.node_a)@self.constraint.free_ik.local_matrix_a
                self.initial_matrix_a=matrix_world(self.node_a)
                self.initial_matrix_b=matrix_world(self.node_b)

                self.work_euler=None
                self.last_quaternion=None

            def update_limits(self):


                # matrix_a=matrix_world(self.node_a)@self.initial_matrix_a.inverted()@self.constraint_initial_matrix
                # matrix_b=matrix_world(self.node_b)@self.initial_matrix_b.inverted()@self.constraint_initial_matrix

                matrix_a=matrix_world(self.node_a)@self.constraint.free_ik.local_matrix_a
                matrix_b=matrix_world(self.node_b)@self.constraint.free_ik.local_matrix_b



                # mode=bpy.context.scene.ragdoll_tools_limits_mode
                mode=Operators.SetLimits.update_mode

                if mode in (0,1):

                    location=matrix_a.inverted()@matrix_b.to_translation()
                    current_location_min,current_location_max=self.get_limits(location_min=True,location_max=True)

                    location_min=mathutils.Vector(min(location[x],current_location_min[x]) for x in range(3))
                    location_max=mathutils.Vector(max(location[x],current_location_max[x]) for x in range(3))

                    self.set_limits(location_min=location_min,location_max=location_max)

                if mode in (0,2):
                    quaternion=matrix_b.to_quaternion().inverted()@matrix_a.to_quaternion()

                    if self.last_quaternion is None:
                        self.work_euler=quaternion.to_euler()
                        self.last_quaternion=quaternion
                    else:

                        self.work_euler.rotate(self.last_quaternion.inverted()@quaternion)
                        self.last_quaternion=quaternion

                    current_rotation_min,current_rotation_max=self.get_limits(rotation_min=True,rotation_max=True)

                    rotation_min=mathutils.Vector(min(self.work_euler[x],current_rotation_min[x]) for x in range(3))
                    rotation_max=mathutils.Vector(max(self.work_euler[x],current_rotation_max[x]) for x in range(3))

                    self.set_limits(rotation_min=rotation_min,rotation_max=rotation_max)

            def reset_constraint(self):
                limit=mathutils.Vector()
                # mode=bpy.context.scene.ragdoll_tools_limits_mode
                mode=Operators.SetLimits.update_mode
                if mode==0: self.set_limits(limit,limit,limit,limit)
                if mode==1: self.set_limits(location_min=limit,location_max=limit)
                if mode==2: self.set_limits(rotation_min=limit,rotation_max=limit)
                self.update_initial()

            def get_limits(self,location_min=False,location_max=False,rotation_min=False,rotation_max=False):
                out=[]
                if location_min:
                    out.append(self.constraint.free_ik_location_min)

                if location_max:
                    out.append(self.constraint.free_ik_location_max)

                if rotation_min:
                    out.append(self.constraint.free_ik_rotation_min)
                if rotation_max:
                    out.append(self.constraint.free_ik_rotation_max)
                return out

            def set_limits(self,location_min=None,location_max=None,rotation_min=None,rotation_max=None):

                if location_min is not None:
                    self.constraint.free_ik_location_min=location_min
                if location_max is not None:
                    self.constraint.free_ik_location_max=location_max

                if rotation_min is not None:
                    self.constraint.free_ik_rotation_min=rotation_min
                if rotation_max is not None:
                    self.constraint.free_ik_rotation_max=rotation_max

        keymap_items=set()

        holders=[]
        valid_holders=[]

        text_handler=None
        limits_handler=None
        shader=gpu.shader.from_builtin('3D_SMOOTH_COLOR')
        shader.bind()

        @staticmethod
        def update_initial():
            Operators.SetLimits.holders=[]
            for source_object in bpy.data.objects:
                holder=Operators.SetLimits.ConstraintHolder(source_object)
                if holder.is_valid:
                    holder.update_initial()
                    Operators.SetLimits.holders.append(holder)

        @staticmethod
        def handler(scene):
            # print("SET LIMITS HANDLER",random.randint(0,100))
            for holder in Operators.SetLimits.holders: holder.validate()
            for holder in Operators.SetLimits.holders:
                if holder.is_valid: holder.update_limits()

        @staticmethod
        def text_handler_source(self,context):

            # print(bpy.context.region)

            if Operators.SetLimits.show_help:
                # mode=bpy.context.scene.ragdoll_tools_limits_mode
                mode=Operators.SetLimits.update_mode
                if mode==0:
                    mode_name="Location and rotation"
                elif mode==1:
                    mode_name="Location only"
                else:
                    mode_name="Rotation only"

                lines=[]
                lines.append(f"Mode: {mode_name}")
                lines.append(" ")
                lines.append("Change mode: Shift-M")
                lines.append("Clear limits: C")
                lines.append(" ")
                lines.append("Toggle help: F1")
                lines.append("Exit: ESC")

                font_id=0
                font_size=15
                blf.size(font_id,font_size,72)
                blf.color(font_id,1,1,1,1)

                current_height=0
                margin=font_size*0.5
                start_x,start_y=0.1*bpy.context.region.width,0.1*bpy.context.region.height

                for line in reversed(lines):
                    blf.position(font_id,start_x,start_y+current_height,0)
                    blf.draw(font_id,line)

                    w,h=blf.dimensions(font_id,line)
                    current_height+=h+margin

        @staticmethod
        def limits_handler_source():

            if bpy.context.space_data.overlay.show_overlays:




                for holder in Operators.SetLimits.holders: holder.validate()
                valid_holders=[holder for holder in Operators.SetLimits.holders if holder.is_valid]


                for holder in valid_holders:
                    visual_matrix=matrix_world(holder.node_a)@holder.constraint.free_ik.local_matrix_a
                    # visual_matrix=matrix_world(holder.node_a)@holder.initial_matrix_a.inverted()@holder.constraint_initial_matrix

                    segments=50
                    radius=holder.constraint.empty_display_size

                    rotation_axes=(mathutils.Vector((1.,0.,0.)),mathutils.Vector((0.,1.,0.)),mathutils.Vector((0.,0.,1.)))
                    axes_to_rotate=(rotation_axes[1],rotation_axes[0],rotation_axes[1])
                    colors=((1.0,0.0,0.0,0.3),(0.0,1.0,0.0,0.3),(0.0,0.0,1.0,0.3))
                    solid_colors=((0.7,0.0,0.0,1.0),(0.0,0.7,0.0,1.0),(0.0,0.0,0.7,1.0))


                    location_min,location_max,rotation_min,rotation_max=holder.get_limits(location_min=True,location_max=True,rotation_min=True,rotation_max=True)


                    linear_matrix=mathutils.Matrix.Translation(visual_matrix.to_translation())@visual_matrix.to_quaternion().to_matrix().to_4x4()



                    points=[]
                    point_colors=[]

                    for angle_min,angle_max,color,rotation_axis,axis_to_rotate in zip(rotation_min,rotation_max,colors,rotation_axes,axes_to_rotate):

                        origin_point=tuple(visual_matrix@mathutils.Vector([0,0,0]))
                        origin_color=0.,0.,0.,0.

                        last_matrix=visual_matrix@mathutils.Matrix.Rotation(-angle_min,4,rotation_axis)
                        last_point=None

                        for x in range(segments+1):

                            point=tuple(last_matrix@(radius*axis_to_rotate))

                            if last_point is not None:
                                point_colors.append(origin_color)
                                points.append(origin_point)

                                point_colors.append(color)
                                points.append(point)

                                point_colors.append(color)
                                points.append(last_point)

                            last_matrix@=mathutils.Matrix.Rotation(-(angle_max-angle_min)/segments,4,rotation_axis)
                            last_point=point




                    bgl.glEnable(bgl.GL_BLEND)
                    bgl.glDisable(bgl.GL_DEPTH_TEST)

                    render_batch=batch_for_shader(Operators.SetLimits.shader,'TRIS',{"pos":points,"color":point_colors})
                    render_batch.draw(Operators.SetLimits.shader)

                    points=[]
                    point_colors=[]
                    bgl.glDisable(bgl.GL_BLEND)
                    bgl.glLineWidth(2)

                    x=0
                    for l_min,l_max,color in zip(location_min,location_max,solid_colors):
                        v_min=mathutils.Vector()
                        v_max=mathutils.Vector()

                        v_min[x]=l_min
                        v_max[x]=l_max

                        v_min=linear_matrix@v_min
                        v_max=linear_matrix@v_max


                        points.append(tuple(v_min))
                        point_colors.append(color)
                        points.append(tuple(v_max))
                        point_colors.append(color)
                        x+=1

                    render_batch=batch_for_shader(Operators.SetLimits.shader,'LINES',{"pos":points,"color":point_colors})
                    render_batch.draw(Operators.SetLimits.shader)

                    bgl.glPointSize(8)
                    render_batch=batch_for_shader(Operators.SetLimits.shader,'POINTS',{"pos":points,"color":point_colors})
                    render_batch.draw(Operators.SetLimits.shader)

        def clear_limits(self):
            for holder in Operators.SetLimits.holders:
                if holder.is_valid:
                    is_selected=False
                    if type(holder.node_b) is bpy.types.PoseBone:
                        is_selected=holder.node_b.bone.select
                    if type(holder.node_b) is bpy.types.Object:
                        is_selected=holder.node_b.select_get()

                    if is_selected:
                        holder.reset_constraint()

        # def draw(self, context):
        #     layout = self.layout
        def stop_handler(self):
            # print(id(handler))
            keymap_items=bpy.context.window_manager.keyconfigs.active.keymaps['Screen Editing'].keymap_items
            if self.handler in bpy.app.handlers.depsgraph_update_post:
                bpy.app.handlers.depsgraph_update_pre.remove(self.handler)

            if Operators.SetLimits.text_handler is not None:
                bpy.types.SpaceView3D.draw_handler_remove(Operators.SetLimits.text_handler,'WINDOW')
                Operators.SetLimits.text_handler=None

                bpy.types.SpaceView3D.draw_handler_remove(Operators.SetLimits.limits_handler,'WINDOW')
                Operators.SetLimits.limits_handler=None

            for item in Operators.SetLimits.keymap_items:
                keymap_items.remove(item)
                Operators.SetLimits.keymap_items=set()

        def execute(self,context):
            # print(id(handler))

            keymap_items=bpy.context.window_manager.keyconfigs.active.keymaps['Screen Editing'].keymap_items

            if self.mode=='START':
                # print("START")
                self.stop_handler()

                self.update_initial()
                bpy.app.handlers.depsgraph_update_pre.append(self.handler)
                Operators.SetLimits.text_handler=bpy.types.SpaceView3D.draw_handler_add(self.text_handler_source,(None,None),
                                                                              'WINDOW','POST_PIXEL')
                Operators.SetLimits.limits_handler=bpy.types.SpaceView3D.draw_handler_add(Operators.SetLimits.limits_handler_source,(),
                                                                                'WINDOW','POST_VIEW')

                item=keymap_items.new(Operators.SetLimits.bl_idname,'ESC','PRESS',head=True)
                item.properties.mode='STOP'
                Operators.SetLimits.keymap_items.add(item)

                item=keymap_items.new(Operators.SetLimits.bl_idname,'C','PRESS',head=True)
                item.properties.mode='CLEAR'
                Operators.SetLimits.keymap_items.add(item)

                item=keymap_items.new(Operators.SetLimits.bl_idname,'F1','PRESS',head=True)
                item.properties.mode='HELP'
                Operators.SetLimits.keymap_items.add(item)

                item=keymap_items.new(Operators.SetLimits.bl_idname,'M','PRESS',shift=True,head=True)
                item.properties.mode='MODE'
                Operators.SetLimits.keymap_items.add(item)

            if self.mode=='STOP':
                # print("STOP")
                self.stop_handler()

            if self.mode=='CLEAR':
                # print("CLEAR")
                self.clear_limits()

            if self.mode=='HELP':
                # print("HELP")
                Operators.SetLimits.show_help=not Operators.SetLimits.show_help

            if self.mode=='MODE':
                Operators.SetLimits.update_mode=(Operators.SetLimits.update_mode+1)%3

            bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP',iterations=1)

            return {'FINISHED'}

    class SetParent(bpy.types.Operator):
        """Set pose parent"""
        bl_idname="free_ik.set_parent"
        bl_label="Set parent"
        bl_options={'REGISTER','UNDO'}

        mode: bpy.props.EnumProperty(name="Mode",
                                     items=[
                                         ('POSE',"Pose parent","Set parent for posing",'NONE',0),
                                         ('FRAME',"Playback parent","Set parent for playback",'NONE',1),
                                     ]
                                     )

        direct: bpy.props.BoolProperty(name="Selected to active",default=True)

        def apply_parent(self,node):

            if self.mode=='POSE': parent_name=gv.pose_parent_name
            if self.mode=='FRAME': parent_name=gv.frame_parent_name

            if parent_name in node.source.constraints:
                parent_holder=node.source.constraints[parent_name]
            else:
                parent_holder=node.source.constraints.new('CHILD_OF')
                parent_holder.name=parent_name
                parent_holder.mute=True

            if node.pose_parent is None:
                parent_holder.target=None
                parent_holder.subtarget=""
            else:

                if type(node.pose_parent.source)==bpy.types.PoseBone:
                    parent_holder.target=node.pose_parent.source.id_data
                    parent_holder.subtarget=node.pose_parent.source.name
                else:
                    parent_holder.target=node.pose_parent.source
                    parent_holder.subtarget=""

            parent_holder.inverse_matrix=node.matrix

        @classmethod
        def poll(self,context):
            return True

        def execute(self,context):
            pass  #print("EXECUTE")

            active_item=None

            if context.mode=='POSE':
                active_item=context.active_pose_bone
                selected_items=context.selected_pose_bones

            if context.mode=='OBJECT':
                active_item=context.object
                selected_items=context.selected_objects

            if active_item is not None and len(selected_items)!=0:

                gvs=gv.structure


                gvs.update_ids()

                items=list(selected_items)
                items.append(active_item)

                for item in items:
                    if item.free_ik.id<0:
                        gvs.make_id(item)
                        item.free_ik.type='NODE'

                gvs.make()

                active_node=gvs.item_node[active_item]
                node_set=set(gvs.item_node[item] for item in selected_items if item!=active_item)



                pairs=[]


                if self.direct:
                    for node in node_set:

                        pairs.append((active_node,node))

                else:
                    graph=Graph(gvs.nodes,gvs.links)
                    for state in graph.width_iterator(active_node):
                        if state.head in node_set:
                            pairs.append([state.tail,state.head])


                if len(pairs)!=0:

                    if self.mode=='POSE':
                        parent=active_node.get_pose_parent()
                        if parent in node_set:
                            item_to_constraint(active_node.source,gv.names.pose_parent_name,None)


                    if self.mode=='FRAME':
                        parent=active_node.get_frame_parent()
                        if parent in node_set:
                            active_node.source.free_ik_frame_parent_id=-1



                for parent,child in pairs:
                    if self.mode=='POSE':
                        item_to_constraint(child.source,gv.names.pose_parent_name,parent.source)

                    if self.mode=='FRAME':
                        child.source.free_ik_frame_parent_id=parent.source.free_ik.id
                        print(compose_matrix(child.get_local_transform()))
                        child.source.free_ik_frame_parent_matrix=flatten( parent.get_matrix().inverted()@child.get_matrix()@compose_matrix(child.get_local_transform()).inverted())










            return {'FINISHED'}

    class ClearParent(bpy.types.Operator):
        """Clear parent"""
        bl_idname="free_ik.clear_parent"
        bl_label="Clear parent"
        bl_options={'REGISTER','UNDO'}

        mode: bpy.props.EnumProperty(name="Mode",
                                     items=[
                                         ('POSE',"Pose parent","Clear pose parent",'NONE',0),
                                         ('FRAME',"Playback parent","Clear playback parent",'NONE',1),
                                     ]
                                     )

        @classmethod
        def poll(self,context):
            return True

        def execute(self,context):
            pass  #print("EXECUTE")



            if context.mode=='POSE':
                selected_items=context.selected_pose_bones

            if context.mode=='OBJECT':
                selected_items=context.selected_objects



            for item in selected_items:
                if self.mode=='POSE':
                    item_to_constraint(item,gv.names.pose_parent_name,None)
                if self.mode=='FRAME':
                    item.free_ik_frame_parent_id=-1


            bpy.context.view_layer.update()



            return {'FINISHED'}

    class SetLinkState(bpy.types.Operator):
        """Enable or disable selected links"""
        bl_idname="free_ik.set_link_state"
        bl_label="Set link state"
        bl_options={'REGISTER','UNDO'}

        link_state: bpy.props.BoolProperty(name="Enabled")

        @classmethod
        def poll(self,context):
            return True

        def check(self,context):
            return True

        def execute(self,context):
            pass  #print("EXECUTE")

            for selected_object in context.selected_objects:
                selected_object.free_ik.is_enabled=self.link_state
                selected_object.update_tag(refresh={'OBJECT'})


            bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP',iterations=1)

            return {'FINISHED'}

    class SetPinState(bpy.types.Operator):
        """Pin or unpin selected"""
        bl_idname="free_ik.set_pin_state"
        bl_label="Set pin state"
        bl_options={'REGISTER','UNDO'}

        pin_state: bpy.props.BoolProperty(name="Pin state")

        @classmethod
        def poll(self,context):
            return True

        def execute(self,context):
            pass  #print("EXECUTE")

            if context.mode=='POSE':
                for selected_bone in context.selected_pose_bones:
                    selected_bone.free_ik_is_pinned=self.pin_state
            if context.mode=='OBJECT':
                for selected_object in context.selected_objects:
                    selected_object.free_ik_is_pinned=self.pin_state

            bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP',iterations=1)

            return {'FINISHED'}

    class Keyframer(bpy.types.Operator):
        """Insert or delete keyframes"""
        bl_idname="free_ik.keyframer"
        bl_label="Keyframer"
        bl_options={'REGISTER','UNDO'}

        mode: bpy.props.EnumProperty(name="Mode",
                                     items=[
                                         ('INSERT',"Insert","Insert",'NONE',0),
                                         ('DELETE',"Delete","Delete",'NONE',1),
                                         ('CLEAR',"Clear","Clear",'NONE',2),
                                     ]
                                     )
        target: bpy.props.EnumProperty(name="Target",
                                       items=[
                                           ('IS_PINNED',"Is pinned","Is pinned",'NONE',0),
                                           ('IS_ENABLED',"Is enabled","Is enabled",'NONE',1),
                                           ('FRAME_PARENT',"Frame parent","Is enabled",'NONE',2),

                                       ]
                                       )


        def keyframe_insert(self,item,data_path,interpolation='CONSTANT',keyframe_type="EXTREME",action_group=""):
            if type(item) is bpy.types.PoseBone:
                base_path=f'pose.bones["{item.name}"].'
                fcurve_holder=item.id_data
            if type(item)==bpy.types.Object:
                base_path=""
                fcurve_holder=item

            value=getattr(item,data_path)

            if type(value) is mathutils.Matrix:points=flatten(value)
            else:points=[value]

            frame=bpy.context.scene.frame_current


            if fcurve_holder.animation_data is None:
                fcurve_holder.animation_data_create()
            if fcurve_holder.animation_data.action is None:
                fcurve_holder.animation_data.action=bpy.data.actions.new("Action")
            for x,point in enumerate(points):
                fcurve=fcurve_holder.animation_data.action.fcurves.find(base_path+data_path,index=x)
                if fcurve is None:
                    fcurve=fcurve_holder.animation_data.action.fcurves.new(base_path+data_path,index=x,action_group=action_group)

                keyframe=fcurve.keyframe_points.insert(frame,point)
                keyframe.type=keyframe_type
                keyframe.interpolation=interpolation

        def keyframe_delete(self,item,data_path):
            if type(item) is bpy.types.PoseBone:fcurve_holder=item.id_data
            if type(item)==bpy.types.Object:fcurve_holder=item
            if fcurve_holder.animation_data is not None:
                if fcurve_holder.animation_data.action is not None:
                    item.keyframe_delete(data_path)

        def keyframe_clear(self,item,data_path):
            if type(item) is bpy.types.PoseBone:
                base_path=f'pose.bones["{item.name}"].'
                fcurve_holder=item.id_data
            if type(item)==bpy.types.Object:
                base_path=""
                fcurve_holder=item

            value=getattr(item,data_path)

            if type(value) is mathutils.Matrix:points=flatten(value)
            else:points=[value]

            if fcurve_holder.animation_data is not None:
                if fcurve_holder.animation_data.action is not None:
                    for x,point in enumerate(points):
                        to_remove=fcurve_holder.animation_data.action.fcurves.find(base_path+data_path,index=x)
                        if to_remove is not None:
                            fcurve_holder.animation_data.action.fcurves.remove(to_remove)

                    fcurve_holder.animation_data.action.fcurves.update()


        @classmethod
        def poll(self,context):
            return True

        def execute(self,context):
            pass  #print("EXECUTE",self.mode,self.target)

            if context.mode=='POSE':selected_items=context.selected_pose_bones
            if context.mode=='OBJECT':selected_items=context.selected_objects

            if self.target=='FRAME_PARENT':
                data_paths=["free_ik_frame_parent_id","free_ik_frame_parent_matrix"]

            for item in selected_items:
                if self.mode=='INSERT':
                    for data_path in data_paths:
                        self.keyframe_insert(item,data_path,action_group=f"{item.name}:Parenting")

                if self.mode=='DELETE':
                    for data_path in data_paths:
                        self.keyframe_delete(item,data_path)
                if self.mode=='CLEAR':
                    for data_path in data_paths:
                        self.keyframe_clear(item,data_path)






            bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP',iterations=1)

            return {'FINISHED'}

    class PastePoseReplacer(bpy.types.Operator):
        bl_idname="free_ik.paste_pose_replacer"
        bl_label="Paste Pose"
        bl_options={'INTERNAL','UNDO','REGISTER'}

        flipped: bpy.props.BoolProperty(name="Flipped on X-Axis",
                                        description="Paste the stored pose flipped on to current pose",default=False)
        selected_mask: bpy.props.BoolProperty(name="On Selected Only",
                                              description="Only paste the stored pose flipped on to selected bones in the current pose",
                                              default=False)

        def execute(self,context):
            # print("PASTE BEGIN",random.randint(0,100))


            gv.operator.is_paste_pose=True

            gv.operator.paste_full=True
            bpy.ops.pose.paste(flipped=self.flipped,selected_mask=False)
            gv.operator.paste_full=False

            gv.operator.paste_partial=True
            bpy.ops.pose.paste(flipped=self.flipped,selected_mask=self.selected_mask)
            gv.operator.paste_partial=False

            gv.operator.is_paste_pose=False


            # print("PASTE END")
            return {'FINISHED'}

    class TransformReplacer(bpy.types.Operator):
        bl_idname="free_ik.transform_replacer"
        bl_label="Transform replacer"
        bl_options={'INTERNAL'}

        mode: bpy.props.EnumProperty(name="Mode",
                                     items=[
                                         ('TRANSLATE',"Translate","Translate",'NONE',0),
                                         ('ROTATE',"Rotate","Rotate",'NONE',1),
                                         ('RESIZE',"Resize","Resize",'NONE',2),
                                         ('MIRROR',"Mirror","Mirror",'NONE',3),
                                         ('BREAKDOWN',"","",'NONE',4),
                                         ('GIZMO',"","",'NONE',5),
                                     ]
                                     )
        modal_start_operator=None
        def execute(self,context):

            gv.operator.modal_transform_start=True
            main_handler()
            gv.operator.modal_transform_start=False

            gv.operator.is_modal_transform=True


            # print("TRANSFORM START")

            Operators.TransformReplacer.modal_start_operator=bpy.context.active_operator
            bpy.ops.free_ik.transform_end('INVOKE_DEFAULT')

            if self.mode=='TRANSLATE':
                bpy.ops.transform.translate('INVOKE_DEFAULT')
            if self.mode=='ROTATE':
                bpy.ops.transform.rotate('INVOKE_DEFAULT')
            if self.mode=='RESIZE':
                bpy.ops.transform.resize('INVOKE_DEFAULT')
            if self.mode=='MIRROR':
                bpy.ops.transform.mirror('INVOKE_DEFAULT')
            if self.mode=='BREAKDOWN':
                try:
                    bpy.ops.pose.breakdown('INVOKE_DEFAULT')
                except RuntimeError as error:
                    self.report({'ERROR'},str(error).split(':')[1][1:-1])

            if self.mode=='GIZMO':
                bpy.ops.gizmogroup.gizmo_tweak('INVOKE_DEFAULT')
            return {'FINISHED'}

    class TransformEnd(bpy.types.Operator):
        bl_idname='free_ik.transform_end'
        bl_label='Transform end'
        bl_options={'INTERNAL'}

        def execute(self,context):
            return {'FINISHED'}

        def modal(self,context,event):
            # print("----",event.type, event.value)

            if event.type not in ('TIMER','TIMER0'):
                if Operators.TransformReplacer.modal_start_operator==context.active_operator:
                    gv.operator.modal_transform_cancel=True
                    main_handler()
                    gv.operator.modal_transform_cancel=False
                    # print("TRANSFORM CANCEL")

                else:
                    gv.operator.modal_transform_confirm=True
                    main_handler()
                    gv.operator.modal_transform_confirm=False
                    # print("TRANSFORM CONFIRM")

                gv.operator.is_modal_transform=False
                # print("TRANSFORM FINISHED")
                return {'FINISHED'}

            return {'RUNNING_MODAL'}

        def invoke(self,context,event):
            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}

class Handlers:

    @persistent
    def scene_before_handler(scene):
        pass
        # os.system('cls')
        # print()
        # print("SCENE BEFORE",random.randint(0,100))
        # print(bpy.context.active_operator)
        # print(make_id(bpy.context.object))
        # print(make_id(bpy.context.object.pose.bones[bpy.context.active_pose_bone.name]))
        # print(id(bpy.context.object.pose.bones[0]))

        if gv.time_to_make_keys:
            make_keys()
            gv.time_to_make_keys=False

        gv.scene=True
        gv.before=True

        main_handler()
        gv.scene=False
        gv.before=False

    @persistent
    def scene_after_handler(scene):
        pass

        # print()
        # print("SCENE AFTER",random.randint(0,100))
        # print(bpy.context.active_operator)

        gv.scene=True
        gv.after=True

        main_handler()
        gv.scene=False
        gv.after=False

    @persistent
    def frame_change_before_handler(scene):
        pass
        # print()
        # print("FRAME BEFORE",random.randint(0,100))
        # print(bpy.context.active_operator)

        gv.frame=True
        gv.before=True

        main_handler()
        gv.frame=False
        gv.before=False

    @persistent
    def frame_change_after_handler(scene,graph):
        pass
        # print()
        # print("FRAME AFTER",random.randint(0,100))
        # print(bpy.context.active_operator)

        gv.frame=True
        gv.after=True

        main_handler()
        gv.frame=False
        gv.after=False







def register_extensions():
    print("REGISTER EXTENSIONS")
    def keyframe_menu_draw_replacer(self,context):
        self.layout.prop(self,"active_key_set",text="Keying set")

        if self.active_key_set=="Transform":
            self.layout.separator()

            row=self.layout.row()
            row.prop(context.scene.compact_keyframe_insert_menu,"use_location")
            row.prop(context.scene.compact_keyframe_insert_menu,"use_rotation")
            row.prop(context.scene.compact_keyframe_insert_menu,"use_scale")

            self.layout.label(text="Local")

            row=self.layout.row()
            row.prop(context.scene.free_ik,"use_local_location",text="Location")
            row.prop(context.scene.free_ik,"use_local_rotation",text="Rotation")
            row.prop(context.scene.free_ik,"use_local_scale",text="Scale")

            self.layout.separator()

            self.layout.prop(context.scene.compact_keyframe_insert_menu,"use_visual")

            if context.mode!='POSE':
                self.layout.prop(context.scene.compact_keyframe_insert_menu,"use_delta")
    if hasattr(bpy.types,"ANIM_OT_compact_keyframe_insert_menu"):
        bpy.types.ANIM_OT_compact_keyframe_insert_menu.draw=keyframe_menu_draw_replacer

    #OBJECT AND BONE
    for bpy_type in (bpy.types.Object,bpy.types.PoseBone) :
        bpy_type.free_ik=bpy.props.PointerProperty(type=Settings.FreeIKNodeSettings)


        bpy_type.free_ik_frame_parent_id=bpy.props.FloatProperty(name="Frame parent",default=-1)

        bpy_type.free_ik_is_enabled=bpy.props.BoolProperty(name="Enabled",default=True)
        bpy_type.free_ik_is_pinned=bpy.props.BoolProperty(name="Pinned",default=False)

        bpy_type.free_ik_stretch_factor=bpy.props.FloatProperty(name="Stretch factor",default=0)

        bpy_type.free_ik_local_location=bpy.props.FloatVectorProperty(name="Local location",subtype='TRANSLATION')

        bpy_type.free_ik_local_quaternion=bpy.props.FloatVectorProperty(name="Local rotation",subtype='QUATERNION',size=4,default=(1,0,0,0))
        bpy_type.free_ik_local_euler=bpy.props.FloatVectorProperty(name="Local rotation",subtype='EULER',size=3)
        bpy_type.free_ik_local_axis_angle=bpy.props.FloatVectorProperty(name="Local rotation",subtype='AXISANGLE',size=4)

        bpy_type.free_ik_local_scale=bpy.props.FloatVectorProperty(name="Local scale",subtype='DIRECTION')

        d=1
        bpy_type.free_ik_location_min=bpy.props.FloatVectorProperty(name="Min location",size=3,default=(-d,-d,-d))
        bpy_type.free_ik_location_max=bpy.props.FloatVectorProperty(name="Max location",size=3,default=(d,d,d))

        angle=math.pi/6
        bpy_type.free_ik_rotation_min=bpy.props.FloatVectorProperty(name="Min rotation",size=3,default=(-angle,-angle,-angle))
        bpy_type.free_ik_rotation_max=bpy.props.FloatVectorProperty(name="Max rotation",size=3,default=(angle,angle,angle))


        bpy_type.free_ik_frame_parent_matrix=bpy.props.FloatVectorProperty(name="Frame parent matrix",subtype='MATRIX',size=16,default=flatten(mathutils.Matrix.Identity(4)))


    #BONE

    #SCENE
    bpy.types.Scene.free_ik=bpy.props.PointerProperty(type=Settings.FreeIKSceneSettings)


def unregister_extensions():
    pass


def make_handlers():
    bpy.app.handlers.depsgraph_update_pre.append(Handlers.scene_before_handler)
    bpy.app.handlers.depsgraph_update_post.append(Handlers.scene_after_handler)
    #
    bpy.app.handlers.frame_change_pre.append(Handlers.frame_change_before_handler)
    bpy.app.handlers.frame_change_post.append(Handlers.frame_change_after_handler)


def clear_handlers():
    bpy.app.handlers.depsgraph_update_pre.remove(Handlers.scene_before_handler)
    bpy.app.handlers.depsgraph_update_post.remove(Handlers.scene_after_handler)

    bpy.app.handlers.frame_change_pre.remove(Handlers.frame_change_before_handler)
    bpy.app.handlers.frame_change_post.remove(Handlers.frame_change_after_handler)
paint_holder=set()
def paint_segments():

    # gv.view.points=[(1,2,3),(0,0,0),(1,0,0)]
    # gv.view.colors=[(0,0,0,1) for point in gv.view.points]
    # gv.view.lines=[(0,1),(1,2),(2,0),(1,0)]


    bgl.glEnable(bgl.GL_BLEND)
    bgl.glDisable(bgl.GL_DEPTH_TEST)
    # bgl.glLineWidth(2)
    width=2
    bgl.glLineWidth(width)
    bgl.glPointSize(width*2)

    shader=gpu.shader.from_builtin('3D_FLAT_COLOR')
    shader.bind()

    lines_batch=batch_for_shader(shader,'LINES',{"pos":gv.view.points,"color":[(0,0,0,1) for point in gv.view.points]},indices=gv.view.lines)
    lines_batch.draw(shader)
    points_batch=batch_for_shader(shader,'POINTS',{"pos":gv.view.points,"color":gv.view.colors})
    points_batch.draw(shader)

register_classes=[
    Settings.FreeIKNodeSettings,
    Settings.FreeIKSceneSettings,

    Operators.FREEIK_MT_FreeIKPieMenu,
    Operators.FreeIKPieMenuStarter,
    Operators.MakeLinks,


    Operators.SetParent,
    Operators.ClearParent,

    Operators.SetLimits,

    Operators.SetLinkState,
    Operators.SetPinState,
    Operators.Keyframer,


    Operators.PastePoseReplacer,
    Operators.TransformReplacer,
    Operators.TransformEnd,


    # Panels.FreeIKConstraintPanel,
    Panels.FreeIKBonePanel,
    Panels.FreeIKObjectPanel,

]

def register():
    # os.system('cls')
    print("I AM REGISTER")

    for register_class in register_classes:
        bpy.utils.register_class(register_class)

    register_extensions()
    make_handlers()

    gv.time_to_make_keys=True

    paint_holder.add(bpy.types.SpaceView3D.draw_handler_add(paint_segments,(),'WINDOW','POST_VIEW'))



def unregister():
    print("I AM UNREGISTER")

    clear_handlers()
    clear_keys()
    unregister_extensions()


    for register_class in reversed(register_classes):
        bpy.utils.unregister_class(register_class)

    for item in paint_holder:
        bpy.types.SpaceView3D.draw_handler_remove(item,'WINDOW')

    # bpy.app.timers.unregister(modal_timer)

    # print("UNREGISTER FINISHED")

