# You are at the top. If you attempt to go any higher
#   you will go beyond the known limits of the code
#   universe where there are most certainly monsters

# might be able to get a speedup where I'm appending move and -move

# to do:
#  use point raycaster to make a cloth_wrap option
#  self colisions
    # maybe do dynamic margins for when cloth is moving fast
#  object collisions
    # collisions need to properly exclude pinned and vertex pinned
#  add bending springs
#  add curl by shortening bending springs on one axis or diagonal
#  independantly scale bending springs and structural to create buckling
#  option to cache animation?
#  Custom Source shape option for animated shapes


# collisions:
# Only need to check one of the edges for groups connected to a vertex    
# for edge to face intersections...
# figure out where the edge hit the face
# figure out which end of the edge is inside the face
# move along the face normal to the surface for the point inside.
# if I reflect by flipping the vel around the face normal
#   if it collides on the bounce it will get caught on the next iteration

# Sewing
# Could create super sewing that doesn't use edges but uses scalars along the edge to place virtual points
#   sort of a barycentric virtual spring. Could even use it to sew to faces if I can think of a ui for where on the face.
# On an all triangle mesh, where sew edges come together there are long strait lines. This probably causes those edges to fold.
#   in other words... creating diagonal springs between these edges will not solve the fold problem. Bend spring could do this.

# Bend springs:
# need to speed things up
# When faces have various sizes, the forces don't add up

# self collision
#   where points are pinned, stuff is all jittery
'''??? Would it make sense to do self collisions with virtual edges ???'''
'''??? Could do dynamic collision margins for stuff moving fast ???'''




bl_info = {
    "name": "Modeling Cloth",
    "author": "Rich Colburn, email: the3dadvantage@gmail.com",
    "version": (1, 0),
    "blender": (2, 78, 0),
    "location": "View3D > Extended Tools > Modeling Cloth",
    "description": "Maintains the surface area of an object so it behaves like cloth",
    "warning": "There might be an angry rhinoceros behind you",
    "wiki_url": "",
    "category": '3D View'}


import bpy
import bmesh
import numpy as np
from numpy import newaxis as nax
from bpy_extras import view3d_utils
import time

#enable_numexpr = True
enable_numexpr = False
if enable_numexpr:
    import numexpr as ne

you_have_a_sense_of_humor = False
#you_have_a_sense_of_humor = True
if you_have_a_sense_of_humor:
    import antigravity


def get_co(ob, arr=None, key=None): # key
    """Returns vertex coords as N x 3"""
    c = len(ob.data.vertices)
    if arr is None:    
        arr = np.zeros(c * 3, dtype=np.float32)
    if key is not None:
        ob.data.shape_keys.key_blocks[key].data.foreach_get('co', arr.ravel())        
        arr.shape = (c, 3)
        return arr
    ob.data.vertices.foreach_get('co', arr.ravel())
    arr.shape = (c, 3)
    return arr


def get_proxy_co(ob, arr, me):
    """Returns vertex coords with modifier effects as N x 3"""
    if arr is None:
        arr = np.zeros(len(me.vertices) * 3, dtype=np.float32)
        arr.shape = (arr.shape[0] //3, 3)    
    c = arr.shape[0]
    me.vertices.foreach_get('co', arr.ravel())
    arr.shape = (c, 3)
    return arr


def triangulate(me, ob=None):
    """Requires a mesh. Returns an index array for viewing co as triangles"""
    obm = bmesh.new()
    obm.from_mesh(me)        
    bmesh.ops.triangulate(obm, faces=obm.faces)
    #obm.to_mesh(me)        
    count = len(obm.faces)    
    #tri_idx = np.zeros(count * 3, dtype=np.int32)        
    #me.polygons.foreach_get('vertices', tri_idx)
    tri_idx = np.array([[v.index for v in f.verts] for f in obm.faces])
    
    # Identify bend spring groups. Each edge gets paired with two points on tips of tris around edge    
    # Restricted to edges with two linked faces on a triangulated version of the mesh
    if ob is not None:
        link_ed = [e for e in obm.edges if len(e.link_faces) == 2]
        ob.bend_eidx = np.array([[e.verts[0].index, e.verts[1].index] for e in link_ed])
        fv = np.array([[[v.index for v in f.verts] for f in e.link_faces] for e in link_ed])
        fv.shape = (fv.shape[0],6)
        ob.bend_tips = np.array([[idx for idx in fvidx if idx not in e] for e, fvidx in zip(ob.bend_eidx, fv)])
    obm.free()
    
    return tri_idx#.reshape(count, 3)


def tri_normals_in_place(object, tri_co):    
    """Takes N x 3 x 3 set of 3d triangles and 
    returns non-unit normals and origins"""
    object.origins = tri_co[:,0]
    object.cross_vecs = tri_co[:,1:] - object.origins[:, nax]
    object.normals = np.cross(object.cross_vecs[:,0], object.cross_vecs[:,1])
    object.nor_dots = np.einsum("ij, ij->i", object.normals, object.normals)
    object.normals /= np.sqrt(object.nor_dots)[:, nax]


def get_tri_normals(tr_co):
    """Takes N x 3 x 3 set of 3d triangles and 
    returns non-unit normals and origins"""
    origins = tr_co[:,0]
    cross_vecs = tr_co[:,1:] - origins[:, nax]
    return cross_vecs, np.cross(cross_vecs[:,0], cross_vecs[:,1]), origins


def closest_points_edge(vec, origin, p):
    '''Returns the location of the point on the edge'''
    vec2 = p - origin
    d = (vec2 @ vec) / (vec @ vec)
    cp = vec * d[:, nax]
    return cp, d


def proxy_in_place(object, me):
    """Overwrite vert coords with modifiers in world space"""
    me.vertices.foreach_get('co', object.co.ravel())
    object.co = apply_transforms(object.ob, object.co)


def apply_rotation(object):
    """When applying vectors such as normals we only need
    to rotate"""
    m = np.array(object.ob.matrix_world)
    mat = m[:3, :3].T
    object.v_normals = object.v_normals @ mat
    

def proxy_v_normals_in_place(object, world=True, me=None):
    """Overwrite vert coords with modifiers in world space"""
    me.vertices.foreach_get('normal', object.v_normals.ravel())
    if world:    
        apply_rotation(object)


def proxy_v_normals(ob, me):
    """Overwrite vert coords with modifiers in world space"""
    arr = np.zeros(len(me.vertices) * 3, dtype=np.float32)
    me.vertices.foreach_get('normal', arr)
    arr.shape = (arr.shape[0] //3, 3)
    m = np.array(ob.matrix_world, dtype=np.float32)    
    mat = m[:3, :3].T # rotates backwards without T
    return arr @ mat


def apply_transforms(ob, co):
    """Get vert coords in world space"""
    m = np.array(ob.matrix_world, dtype=np.float32)    
    mat = m[:3, :3].T # rotates backwards without T
    loc = m[:3, 3]
    return co @ mat + loc


def apply_in_place(ob, arr, cloth):
    """Overwrite vert coords in world space"""
    m = np.array(ob.matrix_world, dtype=np.float32)    
    mat = m[:3, :3].T # rotates backwards without T
    loc = m[:3, 3]
    arr[:] = arr @ mat + loc
    #cloth.co = cloth.co @ mat + loc


def applied_key_co(ob, arr=None, key=None):
    """Get vert coords in world space"""
    c = len(ob.data.vertices)
    if arr is None:
        arr = np.zeros(c * 3, dtype=np.float32)
    ob.data.shape_keys.key_blocks[key].data.foreach_get('co', arr)
    arr.shape = (c, 3)
    m = np.array(ob.matrix_world)    
    mat = m[:3, :3].T # rotates backwards without T
    loc = m[:3, 3]
    return co @ mat + loc


def revert_transforms(ob, co):
    """Set world coords on object. 
    Run before setting coords to deal with object transforms
    if using apply_transforms()"""
    m = np.linalg.inv(ob.matrix_world)    
    mat = m[:3, :3].T # rotates backwards without T
    loc = m[:3, 3]
    return co @ mat + loc  


def revert_in_place(ob, co):
    """Revert world coords to object coords in place."""
    m = np.linalg.inv(ob.matrix_world)    
    mat = m[:3, :3].T # rotates backwards without T
    loc = m[:3, 3]
    co[:] = co @ mat + loc


def revert_rotation(ob, co):
    """When reverting vectors such as normals we only need
    to rotate"""
    #m = np.linalg.inv(ob.matrix_world)    
    m = np.array(ob.matrix_world)
    mat = m[:3, :3] # rotates backwards without T
    return co @ mat


def get_last_object():
    """Finds cloth objects for keeping settings active
    while selecting other objects like pins"""
    cloths = [i for i in bpy.data.objects if i.modeling_cloth] # so we can select an empty and keep the settings menu up
    if bpy.context.object.modeling_cloth:
        return cloths, bpy.context.object
    
    if len(cloths) > 0:
        ob = extra_data['last_object']
        return cloths, ob
    return None, None


def get_poly_centers(ob, type=np.float32, mesh=None):
    mod = False
    m_count = len(ob.modifiers)
    if m_count > 0:
        show = np.zeros(m_count, dtype=np.bool)
        ren_set = np.copy(show)
        ob.modifiers.foreach_get('show_render', show)
        ob.modifiers.foreach_set('show_render', ren_set)
        mod = True
    p_count = len(mesh.polygons)
    center = np.zeros(p_count * 3, dtype=type)
    mesh.polygons.foreach_get('center', center)
    center.shape = (p_count, 3)
    if mod:
        ob.modifiers.foreach_set('show_render', show)

    return center


def simple_poly_centers(ob, key=None):
    if key is not None:
        s_key = ob.data.shape_keys.key_blocks[key].data
        return np.squeeze([[np.mean([ob.data.vertices[i].co for i in p.vertices], axis=0)] for p in ob.data.polygons])


def get_poly_normals(ob, type=np.float32, mesh=None):
    mod = False
    m_count = len(ob.modifiers)
    if m_count > 0:
        show = np.zeros(m_count, dtype=np.bool)
        ren_set = np.copy(show)
        ob.modifiers.foreach_get('show_render', show)
        ob.modifiers.foreach_set('show_render', ren_set)
        mod = True
    p_count = len(mesh.polygons)
    normal = np.zeros(p_count * 3, dtype=type)
    mesh.polygons.foreach_get('normal', normal)
    normal.shape = (p_count, 3)
    if mod:
        ob.modifiers.foreach_set('show_render', show)

    return normal


def get_v_normals(ob, arr, mesh):
    """Since we're reading from a shape key we have to use
    a proxy mesh."""
    mod = False
    m_count = len(ob.modifiers)
    if m_count > 0:
        show = np.zeros(m_count, dtype=np.bool)
        ren_set = np.copy(show)
        ob.modifiers.foreach_get('show_render', show)
        ob.modifiers.foreach_set('show_render', ren_set)
        mod = True
    #v_count = len(mesh.vertices)
    #normal = np.zeros(v_count * 3)#, dtype=type)
    mesh.vertices.foreach_get('normal', arr.ravel())
    #normal.shape = (v_count, 3)
    if mod:
        ob.modifiers.foreach_set('show_render', show)


def get_v_nor(ob, nor_arr):
    ob.data.vertices.foreach_get('normal', nor_arr.ravel())
    return nor_arr


def closest_point_edge(e1, e2, p):
    '''Returns the location of the point on the edge'''
    vec1 = e2 - e1
    vec2 = p - e1
    d = np.dot(vec2, vec1) / np.dot(vec1, vec1)
    cp = e1 + vec1 * d 
    return cp


def create_vertex_groups(groups=['common', 'not_used'], weights=[0.0, 0.0], ob=None):
    '''Creates vertex groups and sets weights. "groups" is a list of strings
    for the names of the groups. "weights" is a list of weights corresponding 
    to the strings. Each vertex is assigned a weight for each vertex group to
    avoid calling vertex weights that are not assigned. If the groups are
    already present, the previous weights will be preserved. To reset weights
    delete the created groups'''
    if ob is None:
        ob = bpy.context.object
    vg = ob.vertex_groups
    for g in range(0, len(groups)):
        if groups[g] not in vg.keys(): # Don't create groups if there are already there
            vg.new(groups[g])
            vg[groups[g]].add(range(0,len(ob.data.vertices)), weights[g], 'REPLACE')
        else:
            vg[groups[g]].add(range(0,len(ob.data.vertices)), 0, 'ADD') # This way we avoid resetting the weights for existing groups.


def get_bmesh(obj=None):
    ob = get_last_object()[1]
    if ob is None:
        ob = obj
    obm = bmesh.new()
    if ob.mode == 'OBJECT':
        obm.from_mesh(ob.data)
    elif ob.mode == 'EDIT':
        obm = bmesh.from_edit_mesh(ob.data)
    return obm


def get_minimal_edges(ob):
    obm = get_bmesh(ob)
    obm.edges.ensure_lookup_table()
    obm.verts.ensure_lookup_table()
    obm.faces.ensure_lookup_table()
    
    # get sew edges:
    sew = [i.index for i in obm.edges if len(i.link_faces)==0]
    
    
    
    # so if I have a vertex with one or more sew edges attached
    # I need to get the mean location of all verts shared by those edges
    # every one of those verts needs to move towards the total mean
    
    
    # get linear edges
    e_count = len(obm.edges)
    eidx = np.zeros(e_count * 2, dtype=np.int32)
    e_bool = np.zeros(e_count, dtype=np.bool)
    e_bool[sew] = True
    ob.data.edges.foreach_get('vertices', eidx)
    eidx.shape = (e_count, 2)

    # get diagonal edges:
    diag_eidx = []
    start = 0
    stop = 0
    step_size = [len(i.verts) for i in obm.faces]
    p_v_count = np.sum(step_size)
    p_verts = np.ones(p_v_count, dtype=np.int32)
    ob.data.polygons.foreach_get('vertices', p_verts)
    # can only be understood on a good day when the coffee flows (uses rolling and slicing)
    # creates uniqe diagonal edge sets
    for f in obm.faces:
        fv_count = len(f.verts)
        stop += fv_count
        if fv_count > 3: # triangles are already connected by linear springs
            skip = 2
            f_verts = p_verts[start:stop]
            for fv in range(len(f_verts)):
                if fv > 1:        # as we go around the loop of verts in face we start overlapping
                    skip = fv + 1 # this lets us skip the overlap so we don't have mirror duplicates
                roller = np.roll(f_verts, fv)
                for r in roller[skip:-1]:
                    diag_eidx.append([roller[0], r])

        start += fv_count    
    
    # eidx groups
    sew_eidx = eidx[e_bool]
    lin_eidx = eidx[~e_bool]
    diag_eidx = np.array(diag_eidx)
    
    # deal with sew verts connected to more than one edge
    s_t_rav = sew_eidx.T.ravel()
    s_uni, s_inv, s_counts = np.unique(s_t_rav,return_inverse=True, return_counts=True)
    s_multi = s_counts > 1
    
    multi_groups = None
    if np.any(s_counts):
        multi_groups = []
        ls = sew_eidx[:,0]
        rs = sew_eidx[:,1]
        
        for i in s_uni[s_multi]:
            gr = np.array([i])
            gr = np.append(gr, ls[rs==i])
            gr = np.append(gr, rs[ls==i])
            multi_groups.append(gr)
        
    return lin_eidx, diag_eidx, sew_eidx, multi_groups


def add_virtual_springs(remove=False):
    cloth = data[get_last_object()[1].name]
    obm = get_bmesh()
    obm.verts.ensure_lookup_table()
    count = len(obm.verts)
    idxer = np.arange(count, dtype=np.int32)
    sel = np.array([v.select for v in obm.verts])    
    selected = idxer[sel]

    if remove:
        ls = cloth.virtual_springs[:, 0]
        
        in_sel = np.in1d(ls, idxer[sel])

        deleter = np.arange(ls.shape[0], dtype=np.int32)[in_sel]
        reduce = np.delete(cloth.virtual_springs, deleter, axis=0)
        cloth.virtual_springs = reduce
        
        if cloth.virtual_springs.shape[0] == 0:
            cloth.virtual_springs.shape = (0, 2)
        return

    existing = np.append(cloth.eidx, cloth.virtual_springs, axis=0)
    flip = existing[:, ::-1]
    existing = np.append(existing, flip, axis=0)
    ls = existing[:,0]
        
    springs = []
    for i in idxer[sel]:

        # to avoid duplicates:
        # where this vert occurs on the left side of the existing spring list
        v_in = existing[i == ls]
        v_in_r = v_in[:,1]
        not_in = selected[~np.in1d(selected, v_in_r)]
        idx_set = not_in[not_in != i]
        for sv in idx_set:
            springs.append([i, sv])
    virtual_springs = np.array(springs, dtype=np.int32)
    
    if virtual_springs.shape[0] == 0:
        virtual_springs.shape = (0, 2)
    
    cloth.virtual_springs = np.append(cloth.virtual_springs, virtual_springs, axis=0)
    # gets appended to eidx in the cloth_init function after calling get connected polys in case geometry changes


def generate_guide_mesh():
    """Makes the arrow that appears when creating pins"""
    verts = [[0.0, 0.0, 0.0], [-0.01, -0.01, 0.1], [-0.01, 0.01, 0.1], [0.01, -0.01, 0.1], [0.01, 0.01, 0.1], [-0.03, -0.03, 0.1], [-0.03, 0.03, 0.1], [0.03, 0.03, 0.1], [0.03, -0.03, 0.1], [-0.01, -0.01, 0.2], [-0.01, 0.01, 0.2], [0.01, -0.01, 0.2], [0.01, 0.01, 0.2]]
    edges = [[0, 5], [5, 6], [6, 7], [7, 8], [8, 5], [1, 2], [2, 4], [4, 3], [3, 1], [5, 1], [2, 6], [4, 7], [3, 8], [9, 10], [10, 12], [12, 11], [11, 9], [3, 11], [9, 1], [2, 10], [12, 4], [6, 0], [7, 0], [8, 0]]
    faces = [[0, 5, 6], [0, 6, 7], [0, 7, 8], [0, 8, 5], [1, 3, 11, 9], [1, 2, 6, 5], [2, 4, 7, 6], [4, 3, 8, 7], [3, 1, 5, 8], [12, 10, 9, 11], [4, 2, 10, 12], [3, 4, 12, 11], [2, 1, 9, 10]]
    name = 'ModelingClothPinGuide'
    if 'ModelingClothPinGuide' in bpy.data.objects:
        mesh_ob = bpy.data.objects['ModelingClothPinGuide']
    else:   
        mesh = bpy.data.meshes.new('ModelingClothPinGuide')
        mesh.from_pydata(verts, edges, faces)  
        mesh.update()
        mesh_ob = bpy.data.objects.new(name, mesh)
        bpy.context.scene.objects.link(mesh_ob)
        mesh_ob.show_x_ray = True
    return mesh_ob


def create_giude():
    """Spawns the guide"""
    if 'ModelingClothPinGuide' in bpy.data.objects:
        mesh_ob = bpy.data.objects['ModelingClothPinGuide']
        return mesh_ob
    mesh_ob = generate_guide_mesh()
    bpy.context.scene.objects.active = mesh_ob
    bpy.ops.object.material_slot_add()
    if 'ModelingClothPinGuide' in bpy.data.materials:
        mat = bpy.data.materials['ModelingClothPinGuide']
    else:    
        mat = bpy.data.materials.new(name='ModelingClothPinGuide')
    mat.use_transparency = True
    mat.alpha = 0.35            
    mat.emit = 2     
    mat.game_settings.alpha_blend = 'ALPHA_ANTIALIASING'
    mat.diffuse_color = (1, 1, 0)
    mesh_ob.material_slots[0].material = mat
    return mesh_ob


def delete_giude():
    """Deletes the arrow"""
    if 'ModelingClothPinGuide' in bpy.data.objects:
        bpy.data.objects.remove(bpy.data.objects['ModelingClothPinGuide'])
    if 'ModelingClothPinGuide' in bpy.data.meshes:        
        guide_mesh = bpy.data.meshes['ModelingClothPinGuide']
        guide_mesh.user_clear()
        bpy.data.meshes.remove(guide_mesh)
    

def scale_source(multiplier):
    """grow or shrink the source shape"""
    ob = get_last_object()[1]
    if ob is not None:
        if ob.modeling_cloth:
            count = len(ob.data.vertices)
            co = np.zeros(count*3, dtype=np.float32)
            ob.data.shape_keys.key_blocks['modeling cloth source key'].data.foreach_get('co', co)
            co.shape = (count, 3)
            mean = np.mean(co, axis=0)
            co -= mean
            co *= multiplier
            co += mean
            ob.data.shape_keys.key_blocks['modeling cloth source key'].data.foreach_set('co', co.ravel())                
            if hasattr(data[ob.name], 'cy_dists'):
                data[ob.name].cy_dists *= multiplier
            

def reset_shapes(ob=None):
    """Sets the modeling cloth key to match the source key.
    Will regenerate shape keys if they are missing"""
    if ob is None:    
        if bpy.context.object.modeling_cloth:
            ob = bpy.context.object
        else:    
            ob = extra_data['last_object']

    if ob.data.shape_keys == None:
        ob.shape_key_add('Basis')    
    if 'modeling cloth source key' not in ob.data.shape_keys.key_blocks:
        ob.shape_key_add('modeling cloth source key')        
    if 'modeling cloth key' not in ob.data.shape_keys.key_blocks:
        ob.shape_key_add('modeling cloth key')        
        ob.data.shape_keys.key_blocks['modeling cloth key'].value=1
    
    keys = ob.data.shape_keys.key_blocks
    count = len(ob.data.vertices)
    co = np.zeros(count * 3, dtype=np.float32)
    keys['Basis'].data.foreach_get('co', co)
    #co = applied_key_co(ob, None, 'modeling cloth source key')
    #keys['modeling cloth source key'].data.foreach_set('co', co)
    keys['modeling cloth key'].data.foreach_set('co', co)
    
    # reset the data stored in the class
    data[ob.name].vel[:] = 0
    co.shape = (co.shape[0]//3, 3)
    data[ob.name].co = co
    
    keys['modeling cloth key'].mute = True
    keys['modeling cloth key'].mute = False


def get_spring_mix(ob, eidx):
    rs = []
    ls = []
    minrl = []
    for i in eidx:
        r = eidx[eidx == i[1]].shape[0]
        l = eidx[eidx == i[0]].shape[0]
        rs.append (min(r,l))
        ls.append (min(r,l))
    mix = 1 / np.array(rs + ls) ** 1.2
    
    return mix
        

def update_pin_group():
    """Updates the cloth data after changing mesh or vertex weight pins"""
    create_instance(new=False)


def collision_data_update(self, context):
    if self.modeling_cloth_self_collision:    
        create_instance(new=False)    


def refresh_noise(self, context):
    if self.name in data:
        zeros = np.zeros(data[self.name].count, dtype=np.float32)
        random = np.random.random(data[self.name].count)
        zeros[:] = random
        data[self.name].noise = ((zeros + -0.5) * self.modeling_cloth_noise * 0.1)[:, nax]


def generate_wind(wind_vec, cloth):
    """Maintains a wind array and adds it to the cloth vel"""    
    
    tri_nor = cloth.normals # non-unit calculated by tri_normals_in_place() per each triangle
    w_vec = revert_rotation(cloth.ob, wind_vec)

    turb = cloth.ob.modeling_cloth_turbulence    
    if turb != 0: 
        w_vec += np.random.random(3).astype(np.float32) * turb * np.mean(w_vec) * 4

    # only blow on verts facing the wind
    perp = np.abs(tri_nor @ w_vec)
    cloth.wind += w_vec
    cloth.wind *= perp[:, nax][:, nax]
    
    # reshape for add.at
    shape = cloth.wind.shape
    cloth.wind.shape = (shape[0] * 3, 3)
    
    cloth.wind *= cloth.tri_mix
    np.add.at(cloth.vel, cloth.tridex.ravel(), cloth.wind)
    cloth.wind.shape = shape


def generate_inflate(cloth):
    """Blow it up baby!"""    
    
    tri_nor = cloth.normals #* cloth.ob.modeling_cloth_inflate # non-unit calculated by tri_normals_in_place() per each triangle
    #tri_nor /= np.einsum("ij, ij->i", tri_nor, tri_nor)[:, nax]
    
    # reshape for add.at
    shape = cloth.inflate.shape
    
    cloth.inflate += tri_nor[:, nax] * cloth.ob.modeling_cloth_inflate# * cloth.tri_mix
    
    cloth.inflate.shape = (shape[0] * 3, 3)
    cloth.inflate *= cloth.tri_mix


    np.add.at(cloth.vel, cloth.tridex.ravel(), cloth.inflate)
    cloth.inflate.shape = shape
    cloth.inflate *= 0


def get_quat(rad, axis):
    theta = (rad * 0.5)
    w = np.cos(theta)
    q_axis = axis * np.sin(theta)[:, nax]
    return w, q_axis


def q_rotate(co, w, axis):
    """Takes an N x 3 numpy array and returns that array rotated around
    the axis by the angle in radians w. (standard quaternion)"""    
    move1 = np.cross(axis, co)
    move2 = np.cross(axis, move1)
    move1 *= w[:, nax]
    return co + (move1 + move2) * 2


def bend_springs(cloth, co, measure=None):
    bend_eidx, tips = cloth.bend_eidx, cloth.bend_tips

    tips_co = co[tips]
    bls, brs = bend_eidx[:,0], bend_eidx[:, 1]
    b_oris = co[bls]
    
    be_vecs = co[brs] - b_oris
    te_vecs = tips_co - b_oris[:, nax]

    bcp_dots = np.einsum('ij,ikj->ik', be_vecs, te_vecs)
    be_dots = np.einsum('ij,ij->i', be_vecs, be_vecs)
    b_div = np.nan_to_num(bcp_dots / be_dots[:, nax])
    
    tcp = be_vecs[:, nax] * b_div[:, :, nax]
    
    # tip vecs from cp
    tcp_vecs = te_vecs - tcp
    tcp_dots = np.einsum('ijk,ijk->ij',tcp_vecs, tcp_vecs) 
    
    u_tcp_vecs = tcp_vecs / np.sqrt(tcp_dots)[:, :, nax]
    
    u_tcp_ls = u_tcp_vecs[:, 0]
    u_tcp_rs = u_tcp_vecs[:, 1]
    
    # dot of unit tri tips around axis
    angle_dot = np.einsum('ij,ij->i', u_tcp_ls, u_tcp_rs)
    
    #paralell = angle_dot < -.9999999
    
    angle = np.arccos(np.clip(angle_dot, -1, 1)) # values outside and arccos gives nan
    #angle = np.arccos(angle_dot) # values outside and arccos gives nan


    # get the angle sign
    tcp_cross = np.cross(u_tcp_vecs[:, 0], u_tcp_vecs[:, 1])
    sign = np.sign(np.einsum('ij,ij->i', be_vecs, tcp_cross))
    
    if measure is None:
        s = np.arccos(angle_dot)
        s *= sign
        s[angle_dot < -.9999999] = np.pi

        return s

    angle *= sign
    # rotate edges with quaternypoos
    u_be_vecs = be_vecs / np.sqrt(be_dots)[:, nax]
    b_dif = angle - measure
    
    l_ws, l_axes = get_quat(b_dif, u_be_vecs)
    r_ws, r_axes = l_ws, -l_axes
    
    # move tcp vecs so their origin is in the middle:
    #u_tcp_vecs *= 0.5    
    
    # should I rotate the unit vecs or the source?
    #   rotating the unit vecs here.
    
    stiff = cloth.ob.modeling_cloth_bend_stiff * 0.0057
    rot_ls = q_rotate(u_tcp_ls, l_ws, l_axes) 
    l_force = (rot_ls - u_tcp_ls) * stiff
    
    rot_rs = q_rotate(u_tcp_rs, r_ws, r_axes)    
    r_force = (rot_rs - u_tcp_rs) * stiff
    
    
    np.add.at(cloth.co, tips[:, 0], l_force)
    np.add.at(cloth.co, tips[:, 1], r_force)
    
    np.subtract.at(cloth.co, bend_eidx.ravel(), np.tile(r_force * .5, 2).reshape(r_force.shape[0] * 2, 3))
    np.subtract.at(cloth.co, bend_eidx.ravel(), np.tile(l_force * .5, 2).reshape(l_force.shape[0] * 2, 3))
    
    return
    cloth.co[tips[:, 0]] += l_force
    cloth.co[tips[:, 1]] += r_force
    
    #cloth.co[bend_eidx] -= l_force
    cloth.co[bend_eidx] -= r_force[:, nax]
    cloth.co[bend_eidx] -= l_force[:, nax]
    #cloth.co[brs] -= r_force
    #print("bend here")

    # will need to read bend springs continuously when using
    # a dynamic source shape. Guess I should do that now...
    
    

    # need the angle at each edge
    # need to get the tips of each tri around each edge
    # should be a pair everywhere there is a link face in
    #   the tri bmesh
    """
    With no sign I just get the dot in radians.
    Rotation should move towards the shortest distance
    to the same dot in radians. 
    
    Without getting the sign at all, it will always rotate
    in the same direction to go back to the target.
    By multiplying the dif by the sign, I can make it spin
    the other way to go back to the target dot in rads
    
    """
    

# sewing functions ---------------->>>
def create_sew_edges():

    bpy.ops.mesh.bridge_edge_loops()
    bpy.ops.mesh.delete(type='ONLY_FACE')
    return
    #highlight a sew edge
    #compare vertex counts
    #subdivide to match counts
    #distribute and smooth back into mesh
    #create sew lines
     

    



# sewing functions ---------------->>>

    
class Cloth(object):
    pass


def create_instance(new=True):
    """Creates instance of cloth object with attributes needed for engine"""
    
    for i in bpy.data.meshes:
        if i.users == 0:
            bpy.data.meshes.remove(i)
    
    if new:
        cloth = Cloth()
        cloth.ob = bpy.context.object # based on what the user has as the active object
        cloth.pin_list = [] # these will not be moved by the engine
        cloth.hook_list = [] # these will be moved by hooks and updated to the engine
        cloth.virtual_springs = np.empty((0,2), dtype=np.int32) # so we can attach points to each other without regard to topology
        cloth.sew_springs = [] # edges with no faces attached can be set to shrink
    
    else: # if we set a modeling cloth object and have something else selected, the most recent object will still have it's settings expose in the ui
        ob = get_last_object()[1]
        cloth = data[ob.name]
        cloth.ob = ob 
    
    # get proxy object
    #proxy = cloth.ob.to_mesh(bpy.context.scene, False, 'PREVIEW')
    # ----------------
    
    bpy.context.scene.objects.active = cloth.ob
    cloth.idxer = np.arange(len(cloth.ob.data.vertices), dtype=np.int32)
    # data only accesible through object mode
    mode = cloth.ob.mode
    if mode == 'EDIT':
        bpy.ops.object.mode_set(mode='OBJECT')
    
    # data is read from a source shape and written to the display shape so we can change the target springs by changing the source shape
    cloth.name = cloth.ob.name
    if cloth.ob.data.shape_keys == None:
        cloth.ob.shape_key_add('Basis')    
    if 'modeling cloth source key' not in cloth.ob.data.shape_keys.key_blocks:
        cloth.ob.shape_key_add('modeling cloth source key')        
    if 'modeling cloth key' not in cloth.ob.data.shape_keys.key_blocks:
        cloth.ob.shape_key_add('modeling cloth key')        
        cloth.ob.data.shape_keys.key_blocks['modeling cloth key'].value=1
    cloth.count = len(cloth.ob.data.vertices)
    
    # we can set a large group's pin state using the vertex group. No hooks are used here
    if 'modeling_cloth_pin' not in cloth.ob.vertex_groups:
        cloth.pin_group = create_vertex_groups(groups=['modeling_cloth_pin'], weights=[0.0], ob=None)
    for i in range(cloth.count):
        try:
            cloth.ob.vertex_groups['modeling_cloth_pin'].weight(i)
        except RuntimeError:
            # assign a weight of zero
            cloth.ob.vertex_groups['modeling_cloth_pin'].add(range(0,len(cloth.ob.data.vertices)), 0.0, 'REPLACE')
    cloth.pin_bool = ~np.array([cloth.ob.vertex_groups['modeling_cloth_pin'].weight(i) for i in range(cloth.count)], dtype=np.bool)

    # unique edges------------>>>
    uni_edges = get_minimal_edges(cloth.ob)
    if len(uni_edges[1]) > 0:   
        cloth.eidx = np.append(uni_edges[0], uni_edges[1], axis=0)
    else:
        cloth.eidx = uni_edges[0]
    #cloth.eidx = uni_edges[0][0]

    if cloth.virtual_springs.shape[0] > 0:
        cloth.eidx = np.append(cloth.eidx, cloth.virtual_springs, axis=0)
    cloth.eidx_tiler = cloth.eidx.T.ravel()    

    mixology = get_spring_mix(cloth.ob, cloth.eidx)
    

    eidx1 = np.copy(cloth.eidx)
    pindexer = np.arange(cloth.count, dtype=np.int32)[cloth.pin_bool]
    unpinned = np.in1d(cloth.eidx_tiler, pindexer)
    cloth.eidx_tiler = cloth.eidx_tiler[unpinned]    
    cloth.unpinned = unpinned

    cloth.sew_edges = uni_edges[2]
    cloth.multi_sew = uni_edges[3]
    
    # unique edges------------>>>
    
    cloth.pcount = pindexer.shape[0]
    cloth.pindexer = pindexer
    
    cloth.sco = np.zeros(cloth.count * 3, dtype=np.float32)
    cloth.ob.data.shape_keys.key_blocks['modeling cloth source key'].data.foreach_get('co', cloth.sco)
    cloth.sco.shape = (cloth.count, 3)
    cloth.co = np.zeros(cloth.count * 3, dtype=np.float32)
    cloth.ob.data.shape_keys.key_blocks['modeling cloth key'].data.foreach_get('co', cloth.co)
    cloth.co.shape = (cloth.count, 3)    
    co = cloth.co
    cloth.vel = np.zeros(cloth.count * 3, dtype=np.float32)
    cloth.vel_start = np.zeros(cloth.count * 3, dtype=np.float32)
    cloth.vel_start.shape = (cloth.count, 3)
    cloth.vel.shape = (cloth.count, 3)
    cloth.self_col_vel = np.copy(co)
    
    cloth.v_normals = np.zeros(co.shape, dtype=np.float32)
    #get_v_normals(cloth.ob, cloth.v_normals, proxy)
    
    #noise---
    noise_zeros = np.zeros(cloth.count, dtype=np.float32)
    random = np.random.random(cloth.count).astype(np.float32)
    noise_zeros[:] = random
    cloth.noise = ((noise_zeros + -0.5) * cloth.ob.modeling_cloth_noise * 0.1)[:, nax]
    
    cloth.waiting = False
    cloth.clicked = False # for the grab tool
    
    # this helps with extra springs behaving as if they had more mass---->>>
    cloth.mix = mixology[unpinned][:, nax]
    # -------------->>>

    # new self collisions:
    cloth.tridex = triangulate(cloth.ob.data, cloth)
    cloth.tridexer = np.arange(cloth.tridex.shape[0], dtype=np.int32)
    cloth.tri_co = cloth.co[cloth.tridex]
    tri_normals_in_place(cloth, cloth.tri_co) # non-unit normals
    # -------------->>>
    
    tri_uni, tri_inv, tri_counts = np.unique(cloth.tridex, return_inverse=True, return_counts=True)
    cloth.tri_mix = (1 / tri_counts[tri_inv])[:, nax]
    
    cloth.wind = np.zeros(cloth.tri_co.shape, dtype=np.float32)
    cloth.inflate = np.zeros(cloth.tri_co.shape, dtype=np.float32)

    bpy.ops.object.mode_set(mode=mode)
    
    
    # for use with a static source shape:
    cloth.source_angles = bend_springs(cloth, cloth.sco, None)
    svecs = cloth.sco[cloth.eidx[:, 1]] - cloth.sco[cloth.eidx[:, 0]]
    cloth.sdots = np.einsum('ij,ij->i', svecs, svecs)
    
    # for doing static cling
    #   cloth.col_idx = np.array([], dtype=np.int32)
    #   cloth.re_col = np.empty((0,3), dtype=np.float32)
    
    return cloth


def run_handler(cloth):
    T = time.time()
    if cloth.ob.modeling_cloth_handler_frame | cloth.ob.modeling_cloth_handler_scene:
        if cloth.ob.mode == 'EDIT':
            cloth.waiting = True
        if cloth.waiting:    
            if cloth.ob.mode == 'OBJECT':
                update_pin_group()

        if not cloth.waiting:
            eidx = cloth.eidx # world's most important variable
            cloth.ob.data.shape_keys.key_blocks['modeling cloth source key'].data.foreach_get('co', cloth.sco.ravel())

            sco = cloth.sco
            co = cloth.co
            
            co[cloth.pindexer] += cloth.noise[cloth.pindexer]
            #co += cloth.noise
            cloth.noise *= cloth.ob.modeling_cloth_noise_decay

            # mix in vel before collisions and sewing
            co[cloth.pindexer] += cloth.vel[cloth.pindexer]
            cloth.vel_start[:] = co

            # measure source -------------------------->>>
            dynamic = True # can store for speedup if source shape is static
            # bend spring calculations:
            if cloth.ob.modeling_cloth_bend_stiff != 0:
            
                # measure bend source if using dynamic source:
                source_angles = cloth.source_angles
                if dynamic:    
                    source_angles = bend_springs(cloth, sco, None)
            
            # linear spring measure
            sdots = cloth.sdots
            if dynamic:    
                cloth.ob.data.shape_keys.key_blocks['modeling cloth source key'].data.foreach_get('co', sco.ravel())
                svecs = sco[eidx[:, 1]] - sco[eidx[:, 0]]
                sdots = np.einsum('ij,ij->i', svecs, svecs)            
            # ----------------------------------------->>>
            
            force = cloth.ob.modeling_cloth_spring_force
            mix = cloth.mix * force
            
            ers = eidx[:, 1]
            els = eidx[:, 0]
            for x in range(cloth.ob.modeling_cloth_iterations):    

                # bend spring calculations:
                if cloth.ob.modeling_cloth_bend_stiff != 0:
                    bend_springs(cloth, co, source_angles)

                # add pull
                vecs = co[ers] - co[els]
                dots = np.einsum('ij,ij->i', vecs, vecs)
                div = np.nan_to_num(sdots / dots)
                swap = vecs * np.sqrt(div)[:, nax]
                move = vecs - swap

                # pull separate test--->>>
                push = cloth.ob.modeling_cloth_push_springs
                if push == 0:
                    move[div > 1] = 0
                else:
                    move[div > 1] *= push
                # pull only test--->>>
                
                tiled_move = np.append(move, -move, axis=0)[cloth.unpinned] * mix # * mix for stability: force multiplied by 1/number of springs
                
                np.add.at(cloth.co, cloth.eidx_tiler, tiled_move)
                
                # for doing static cling
                #   cloth.co[cloth.col_idx] = cloth.re_col

                cloth.co[~cloth.pin_bool] = cloth.vel_start[~cloth.pin_bool]

                if len(cloth.pin_list) > 0:
                    hook_co = np.array([cloth.ob.matrix_world.inverted() * i.matrix_world.to_translation() for i in cloth.hook_list])
                    cloth.co[cloth.pin_list] = hook_co
                
                # grab inside spring iterations
                if cloth.clicked: # for the grab tool
                    cloth.co[extra_data['vidx']] = np.array(extra_data['stored_vidx']) + np.array(+ extra_data['move'])   


            # refresh normals for inflate wind and self collisions
            cloth.tri_co = cloth.co[cloth.tridex]
            tri_normals_in_place(cloth, cloth.tri_co) # unit normals


            
            # add effects of velocity and Gravity to the vel array for later
            spring_dif = cloth.co - cloth.vel_start
            
            #if cloth.ob.modeling_cloth_bend_stiff > 0:
                #for
            
            # non-unit normals might be better for inflate and wind because
            # their strength is affected by the area as it is should be
            
            #place after wind and inflate unless those are added to vel after collisions
              
    
            # get proxy object
            #proxy = cloth.ob.to_mesh(bpy.context.scene, False, 'PREVIEW')
            #proxy = cloth.ob.data
            #get_v_normals(cloth.ob, cloth.v_normals, proxy)





            # gravity
            grav = cloth.ob.modeling_cloth_gravity * 0.01# / cloth.ob.modeling_cloth_iterations)
            if grav != 0:
                cloth.vel += revert_rotation(cloth.ob, np.array([0, 0, grav])) / np.array(cloth.ob.scale)
            
            # can cheat here:
            #spring_mean = np.mean(spring_dif, axis=0)
            #cloth.vel += spring_mean * 20

            # inextensible calc:
            cloth.vel += spring_dif * 2



            # The amount of drag increases with speed. 
            # have to convert to to a range between 0 and 1
            #squared_move_dist = np.sqrt(np.einsum("ij, ij->i", cloth.vel, cloth.vel))
            squared_move_dist = np.einsum("ij, ij->i", cloth.vel, cloth.vel)
            squared_move_dist += 1
            cloth.vel *= (1 / (squared_move_dist / cloth.ob.modeling_cloth_velocity))[:, nax]
            #cloth.vel *= cloth.ob.modeling_cloth_velocity

            # wind:
            x = cloth.ob.modeling_cloth_wind_x
            y = cloth.ob.modeling_cloth_wind_y
            z = cloth.ob.modeling_cloth_wind_z
            wind_vec = np.array([x,y,z])
            check_wind = wind_vec != 0
            if np.any(check_wind):
                generate_wind(wind_vec, cloth)            

            # inflate
            inflate = cloth.ob.modeling_cloth_inflate
            if inflate != 0:
                generate_inflate(cloth)
                #cloth.v_normals *= inflate
                #cloth.vel += cloth.v_normals

            

            if cloth.ob.modeling_cloth_sew != 0:
                if len(cloth.sew_edges) > 0:
                    sew_edges = cloth.sew_edges
                    rs = co[sew_edges[:,1]]
                    ls = co[sew_edges[:,0]]
                    sew_vecs = (rs - ls) * 0.5 * cloth.ob.modeling_cloth_sew
                    co[sew_edges[:,1]] -= sew_vecs
                    co[sew_edges[:,0]] += sew_vecs
                    
                    # for sew verts with more than one sew edge
                    if cloth.multi_sew is not None:
                        for sg in cloth.multi_sew:
                            cosg = co[sg]
                            meanie = np.mean(cosg, axis=0)
                            sg_vecs = meanie - cosg
                            co[sg] += sg_vecs * cloth.ob.modeling_cloth_sew
                
            # !!!!!  need to try adding in the velocity before doing the collision stuff
            # !!!!! so vel would be added here after wind and inflate but before collision
            
            
            # floor ---
            if cloth.ob.modeling_cloth_floor:    
                floored = cloth.co[:,2] < 0        
                cloth.vel[:,2][floored] *= -1
                cloth.vel[floored] *= .1
                cloth.co[:, 2][floored] = 0
            # floor ---            
            

            # objects ---
            #T = time.time()
            if cloth.ob.modeling_cloth_object_detect:
                if cloth.ob.modeling_cloth_self_collision:
                    self_collide(cloth)
                
                if extra_data['colliders'] is not None:
                    for i, val in extra_data['colliders'].items():
                        #if val.ob.modeling_cloth_self_collision == cloth.ob:    
                            #self_collide(cloth, val)
                            #cloth.co[cloth.pindexer] = cloth.vel_start[cloth.pindexer]
                        if val.ob != cloth.ob:
                            object_collide(cloth, val)
            #print(time.time()-T, "the whole enchalada")
            # objects ---

            cloth.co[~cloth.pin_bool] = cloth.vel_start[~cloth.pin_bool]
            
            if len(cloth.pin_list) > 0:
                cloth.co[cloth.pin_list] = hook_co
                cloth.vel[cloth.pin_list] = 0

            if cloth.clicked: # for the grab tool
                cloth.co[extra_data['vidx']] = np.array(extra_data['stored_vidx']) + np.array(+ extra_data['move'])


            cloth.ob.data.shape_keys.key_blocks['modeling cloth key'].data.foreach_set('co', cloth.co.ravel())

            cloth.ob.data.shape_keys.key_blocks['modeling cloth key'].mute = True
            cloth.ob.data.shape_keys.key_blocks['modeling cloth key'].mute = False

            # remove proxy
            #proxy.user_clear()
            #bpy.data.meshes.remove(proxy)
            #del(proxy)
    #print(time.time()-T, "the entire handler time")

# +++++++++++++ object collisions ++++++++++++++
def bounds_check(co1, co2, fudge):
    """Returns True if object bounding boxes intersect.
    Have to add the fudge factor for collision margins"""
    check = False
    co1_max = None # will never return None if check is true
    co1_min = np.min(co1, axis=0)
    co2_max = np.max(co2, axis=0)

    if np.all(co2_max + fudge > co1_min):
        co1_max = np.max(co1, axis=0)
        co2_min = np.min(co2, axis=0)        
        
        if np.all(co1_max > co2_min - fudge):
            check = True

    return check, co1_min, co1_max # might as well reuse the checks


def triangle_bounds_check(tri_co, co_min, co_max, idxer, fudge):
    """Returns a bool aray indexing the triangles that
    intersect the bounds of the object"""

    # min check cull step 1
    tri_min = np.min(tri_co, axis=1) - fudge
    check_min = co_max > tri_min
    in_min = np.all(check_min, axis=1)
    
    # max check cull step 2
    idx = idxer[in_min]
    tri_max = np.max(tri_co[in_min], axis=1) + fudge
    check_max = tri_max > co_min
    in_max = np.all(check_max, axis=1)
    in_min[idx[~in_max]] = False
    
    return in_min, tri_min[in_min], tri_max[in_max] # can reuse the min and max


def tri_back_check(co, tri_min, tri_max, idxer, fudge):
    """Returns a bool aray indexing the vertices that
    intersect the bounds of the culled triangles"""

    # min check cull step 1
    tb_min = np.min(tri_min, axis=0) - fudge
    check_min = co > tb_min
    in_min = np.all(check_min, axis=1)
    idx = idxer[in_min]
    
    # max check cull step 2
    tb_max = np.max(tri_max, axis=0) + fudge
    check_max = co[in_min] < tb_max
    in_max = np.all(check_max, axis=1)        
    in_min[idx[~in_max]] = False    
    
    return in_min 


# -------------------------------------------------------
# -------------------------------------------------------
def zxy_grid(co_y, tymin, tymax, subs, c, t, c_peat, t_peat):
    # create linespace grid between bottom and top of tri z
    #subs = 7
    t_min = np.min(tymin)
    t_max = np.max(tymax)
    divs = np.linspace(t_min, t_max, num=subs, dtype=np.float32)            
    
    # figure out which triangles and which co are in each section
    co_bools = (co_y > divs[:-1][:, nax]) & (co_y < divs[1:][:, nax])
    tri_bools = (tymin < divs[1:][:, nax]) & (tymax > divs[:-1][:, nax])

    for i, j in zip(co_bools, tri_bools):
        if (np.sum(i) > 0) & (np.sum(j) > 0):
            c3 = c[i]
            t3 = t[j]
        
            c_peat.append(np.repeat(c3, t3.shape[0]))
            t_peat.append(np.tile(t3, c3.shape[0]))


def zx_grid(co_x, txmin, txmax, subs, c, t, c_peat, t_peat, co_y, tymin, tymax):
    # create linespace grid between bottom and top of tri z
    #subs = 7
    t_min = np.min(txmin)
    t_max = np.max(txmax)
    divs = np.linspace(t_min, t_max, num=subs, dtype=np.float32)            
    
    # figure out which triangles and which co are in each section
    co_bools = (co_x > divs[:-1][:, nax]) & (co_x < divs[1:][:, nax])
    tri_bools = (txmin < divs[1:][:, nax]) & (txmax > divs[:-1][:, nax])

    for i, j in zip(co_bools, tri_bools):
        if (np.sum(i) > 0) & (np.sum(j) > 0):
            c2 = c[i]
            t2 = t[j]
            
            zxy_grid(co_y[i], tymin[j], tymax[j], subs, c2, t2, c_peat, t_peat)


def z_grid(co_z, tzmin, tzmax, subs, co_x, txmin, txmax, co_y, tymin, tymax):
    # create linespace grid between bottom and top of tri z
    #subs = 7
    t_min = np.min(tzmin)
    t_max = np.max(tzmax)
    divs = np.linspace(t_min, t_max, num=subs, dtype=np.float32)
            
    # figure out which triangles and which co are in each section
    co_bools = (co_z > divs[:-1][:, nax]) & (co_z < divs[1:][:, nax])
    tri_bools = (tzmin < divs[1:][:, nax]) & (tzmax > divs[:-1][:, nax])

    c_ranger = np.arange(co_bools.shape[1])
    t_ranger = np.arange(tri_bools.shape[1])

    c_peat = []
    t_peat = []

    for i, j in zip(co_bools, tri_bools):
        if (np.sum(i) > 0) & (np.sum(j) > 0):
            c = c_ranger[i]
            t = t_ranger[j]

            zx_grid(co_x[i], txmin[j], txmax[j], subs, c, t, c_peat, t_peat, co_y[i], tymin[j], tymax[j])
    
    if (len(c_peat) == 0) | (len(t_peat) == 0):
        return None, None
    
    return np.hstack(c_peat), np.hstack(t_peat)
# -------------------------------------------------------
# -------------------------------------------------------    

    
"""Combined with numexpr the first check min and max is faster
    Combined without numexpr is slower. It's better to separate min and max"""
def v_per_tri(co, tri_min, tri_max, idxer, tridexer, c_peat=None, t_peat=None):
    """Checks each point against the bounding box of each triangle"""

    co_x, co_y, co_z = co[:, 0], co[:, 1], co[:, 2]
    
    subs = 7
    #subs = bpy.data.objects['Plane.002'].modeling_cloth_grid_size
    
    c_peat, t_peat = z_grid(co_z, tri_min[:, 2], tri_max[:, 2], subs, co_x, tri_min[:, 0], tri_max[:, 0], co_y, tri_min[:, 1], tri_max[:, 1])
    if c_peat is None:
        return
    # X
    # Step 1 check x_min (because we're N squared here we break it into steps)
    check_x_min = co_x[c_peat] > tri_min[:, 0][t_peat]
    c_peat = c_peat[check_x_min]
    if c_peat.shape[0] == 0:
        return
    t_peat = t_peat[check_x_min]

    # Step 2 check x max
    check_x_max = co_x[c_peat] < tri_max[:, 0][t_peat]
    c_peat = c_peat[check_x_max]
    if c_peat.shape[0] == 0:
        return
    t_peat = t_peat[check_x_max]
    
    # Y
    # Step 3 check y min    
    check_y_min = co_y[c_peat] > tri_min[:, 1][t_peat]
    c_peat = c_peat[check_y_min]
    if c_peat.shape[0] == 0:
        return
    t_peat = t_peat[check_y_min]

    # Step 4 check y max
    check_y_max = co_y[c_peat] < tri_max[:, 1][t_peat]
    c_peat = c_peat[check_y_max]
    if c_peat.shape[0] == 0:
        return
    t_peat = t_peat[check_y_max]

    # Z
    # Step 5 check z min    
    check_z_min = co_z[c_peat] > tri_min[:, 2][t_peat]
    c_peat = c_peat[check_z_min]
    if c_peat.shape[0] == 0:
        return
    t_peat = t_peat[check_z_min]

    # Step 6 check y max
    check_z_max = co_z[c_peat] < tri_max[:, 2][t_peat]
    c_peat = c_peat[check_z_max]
    if c_peat.shape[0] == 0:
        return
    t_peat = t_peat[check_z_max]    

    return idxer[c_peat], t_peat
    #return c_peat, t_peat


def inside_triangles(tri_vecs, v2, co, tri_co_2, cidx, tidx, nor, ori, in_margin, offset=None):
    idxer = np.arange(in_margin.shape[0], dtype=np.int32)[in_margin]
    
    r_co = co[cidx[in_margin]]    
    r_tri = tri_co_2[tidx[in_margin]]
    
    v0 = tri_vecs[:,0]
    v1 = tri_vecs[:,1]
    
    d00_d11 = np.einsum('ijk,ijk->ij', tri_vecs, tri_vecs)
    d00 = d00_d11[:,0]
    d11 = d00_d11[:,1]
    d01 = np.einsum('ij,ij->i', v0, v1)
    d02 = np.einsum('ij,ij->i', v0, v2)
    d12 = np.einsum('ij,ij->i', v1, v2)

    div = 1 / (d00 * d11 - d01 * d01)
    u = (d11 * d02 - d01 * d12) * div
    v = (d00 * d12 - d01 * d02) * div
    
    # !!! Watch out for this number. It could affect speed !!! 
    if offset:
        check = (u > -offset) & (v > -offset) & (u + v < offset + 1)
    else:
        check = (u > 0) & (v > 0) & (u + v < 1)
    in_margin[idxer] = check


def object_collide(cloth, object):
    
    # for doing static cling
    #   cloth.col_idx = np.array([], dtype=np.int32)
    #   cloth.re_col = np.empty((0,3), dtype=np.float32)
    
    proxy = object.ob.to_mesh(bpy.context.scene, True, 'PREVIEW')
    proxy_in_place(object, proxy)
    apply_in_place(cloth.ob, cloth.co, cloth)

    inner_margin = object.ob.modeling_cloth_inner_margin
    outer_margin = object.ob.modeling_cloth_outer_margin
    fudge = max(inner_margin, outer_margin)

    # check object bounds: (need inner and out margins to adjust box size)
    box_check, co1_min, co1_max = bounds_check(cloth.co, object.co, fudge)
    # check for triangles inside the cloth bounds
    #anim = object.ob.modeling_cloth_collision_animated
    
    
    if box_check:

        proxy_v_normals_in_place(object, True, proxy)
        tri_co = object.co[object.tridex]
        tri_vo = object.vel[object.tridex]

        tris_in, tri_min, tri_max = triangle_bounds_check(tri_co, co1_min, co1_max, object.tridexer, fudge)#, object.ob.dimensions)

        # check for verts in the bounds around the culled triangles
        if np.any(tris_in):    
            tri_co_2 = tri_co[tris_in]
            back_check = tri_back_check(cloth.co, tri_min, tri_max, cloth.idxer, fudge)

            # begin every vertex co against every tri
            if np.any(back_check):
                v_tris = v_per_tri(cloth.co[back_check], tri_min, tri_max, cloth.idxer[back_check], object.tridexer[tris_in])

                if v_tris is not None:
                    # update the normals. cross_vecs used by barycentric tri check
                    # move the surface along the vertex normals by the outer margin distance
                    marginalized = (object.co + object.v_normals * outer_margin)[object.tridex]
                    tri_normals_in_place(object, marginalized)
                    
                    # add normals to make extruded tris
                    u_norms = object.normals[tris_in]
                    #u_norms = norms_2 / np.sqrt(np.einsum('ij, ij->i', norms_2, norms_2))[:, nax] 
                                        
                    cidx, tidx = v_tris
                    ori = object.origins[tris_in][tidx]
                    nor = u_norms[tidx]
                    vec2 = cloth.co[cidx] - ori
                    
                    d = np.einsum('ij, ij->i', nor, vec2) # nor is unit norms
                    in_margin = (d > -(inner_margin + outer_margin)) & (d < 0)#outer_margin) (we have offset outer margin)
                    
                    # <<<--- Inside triangle check --->>>
                    # will overwrite in_margin:
                    cross_2 = object.cross_vecs[tris_in][tidx][in_margin]
                    inside_triangles(cross_2, vec2[in_margin], cloth.co, marginalized[tris_in], cidx, tidx, nor, ori, in_margin)
                    
                    if np.any(in_margin):
                        # collision response --------------------------->>>
                        #if anim:    
                        t_in = tidx[in_margin]
                        
                        tri_vo = tri_vo[tris_in]
                        tri_vel1 = np.mean(tri_co_2[t_in], axis=1)
                        tri_vel2 = np.mean(tri_vo[t_in], axis=1)
                        tvel = tri_vel1 - tri_vel2

                        col_idx = cidx[in_margin] 
                        cloth.co[col_idx] -= nor[in_margin] * (d[in_margin])[:, nax]
                        cloth.vel[col_idx] = tvel
                        
                        # for doing static cling
                        #   cloth.re_col = np.copy(cloth.co[col_idx])                        
                        #   cloth.col_idx = col_idx
                        
    object.vel[:] = object.co    
    revert_in_place(cloth.ob, cloth.co)
    
    #temp_ob = bpy.data.objects.new('__TEMP', proxy)
    #for key in proxy.shape_keys.key_blocks:
    #    temp_ob.shape_key_remove(key)
            
    #bpy.data.objects.remove(temp_ob)
    bpy.data.meshes.remove(proxy)


# self collider =============================================
def self_collide(cloth):

    margin = cloth.ob.modeling_cloth_self_collision_margin

    tri_co = cloth.tri_co

    tri_min = np.min(tri_co, axis=1) - margin
    tri_max = np.max(tri_co, axis=1) + margin   

    # begin every vertex co against every tri
    v_tris = v_per_tri(cloth.co, tri_min, tri_max, cloth.idxer, cloth.tridexer)
    if v_tris is not None:
        cidx, tidx = v_tris

        u_norms = cloth.normals

        # don't check faces the verts are part of        
        check_neighbors = cidx[:, nax] == cloth.tridex[tidx]
        cull = np.any(check_neighbors, axis=1)
        cidx, tidx = cidx[~cull], tidx[~cull]
        
        ori = cloth.origins[tidx]
        nor = u_norms[tidx]
        vec2 = cloth.co[cidx] - ori
        
        d = np.einsum('ij, ij->i', nor, vec2) # nor is unit norms
        in_margin = (d > -margin) & (d < margin)
        # <<<--- Inside triangle check --->>>
        # will overwrite in_margin:
        cross_2 = cloth.cross_vecs[tidx][in_margin]
        inside_triangles(cross_2, vec2[in_margin], cloth.co, tri_co, cidx, tidx, nor, ori, in_margin, offset=0.0)
        
        if np.any(in_margin):
            # collision response --------------------------->>>
            t_in = tidx[in_margin]
            #tri_vel1 = np.mean(tri_co[t_in], axis=1)
            #tvel = np.mean(tri_vo[t_in], axis=1)
            #tvel = tri_vel1 - tri_vel2
            t_vel = np.mean(cloth.vel[cloth.tridex][t_in], axis=1)
            
            col_idx = cidx[in_margin] 
            d_in = d[in_margin]
    
            sign_margin = margin * np.sign(d_in) # which side of the face
            c_move = ((nor[in_margin] * d_in[:, nax]) - (nor[in_margin] * sign_margin[:, nax]))#) * -np.sign(d[in_margin])[:, nax]
            #c_move *= 1 / cloth.ob.modeling_cloth_grid_size
            #cloth.co[col_idx] -= ((nor[in_margin] * d_in[:, nax]) - (nor[in_margin] * sign_margin[:, nax]))#) * -np.sign(d[in_margin])[:, nax]
            cloth.co[col_idx] -= c_move #* .7
            #cloth.vel[col_idx] = 0
            cloth.vel[col_idx] = t_vel

    #object.vel[:] = object.co    
# self collider =============================================


# update functions --------------------->>>    
def tile_and_remove_neighbors(vidx, tidx, c_peat, t_peat):

    tshape = tidx.shape[0]
    vshape = vidx.shape[0]

    # eliminate tris that contain the point: 
    # check the speed difference of doing a reshape with ravel at the end
    co_tidex = c_peat.reshape(vshape, tshape)
    tri_tidex = tidx[t_peat.reshape(vshape, tshape)]
    check = tri_tidex == vidx[co_tidex][:,:,nax]
    cull = ~np.any(check, axis=2)

    # duplicate of each tri for each vert and each vert for each tri
    c_peat = c_peat[cull.ravel()]
    t_peat = t_peat[cull.ravel()]
    
    return c_peat, t_peat


class Collider(object):
    pass


class SelfCollider(object):
    pass


def create_collider():
    col = Collider()
    col.ob = bpy.context.object

    # get proxy
    proxy = col.ob.to_mesh(bpy.context.scene, True, 'PREVIEW')
    
    col.co = get_proxy_co(col.ob, None, proxy)
    col.idxer = np.arange(col.co.shape[0], dtype=np.int32)
    proxy_in_place(col, proxy)
    col.v_normals = proxy_v_normals(col.ob, proxy)
    col.vel = np.copy(col.co)
    col.tridex = triangulate(proxy)
    col.tridexer = np.arange(col.tridex.shape[0], dtype=np.int32)
    # cross_vecs used later by barycentric tri check
    proxy_v_normals_in_place(col, True, proxy)
    marginalized = col.co + col.v_normals * col.ob.modeling_cloth_outer_margin
    col.cross_vecs, col.origins, col.normals = get_tri_normals(marginalized[col.tridex])    
    
    # remove proxy
    bpy.data.meshes.remove(proxy)
    return col


# Self collision object
def create_self_collider():
    # maybe fixed? !!! bug where first frame of collide uses empty data. Stuff goes flying.
    col = Collider()
    col.ob = bpy.context.object
    col.co = get_co(col.ob, None)
    proxy_in_place(col)
    col.v_normals = proxy_v_normals(col.ob)
    col.vel = np.copy(col.co)
    #col.tridex = triangulate(col.ob)
    col.tridexer = np.arange(col.tridex.shape[0], dtype=np.int32)
    # cross_vecs used later by barycentric tri check
    proxy_v_normals_in_place(col)
    marginalized = col.co + col.v_normals * col.ob.modeling_cloth_outer_margin
    col.cross_vecs, col.origins, col.normals = get_tri_normals(marginalized[col.tridex])    

    return col


# collide object updater
def collision_object_update(self, context):
    """Updates the collider object"""    
    collide = self.modeling_cloth_object_collision
    # remove objects from dict if deleted
    cull_list = []
    if 'colliders' in extra_data:
        if extra_data['colliders'] is not None:   
            if not collide:
                if self.name in extra_data['colliders']:
                    del(extra_data['colliders'][self.name])
            for i in extra_data['colliders']:
                remove = True
                if i in bpy.data.objects:
                    if bpy.data.objects[i].type == "MESH":
                        if bpy.data.objects[i].modeling_cloth_object_collision:
                            remove = False
                if remove:
                    cull_list.append(i)
    for i in cull_list:
        del(extra_data['colliders'][i])

    # add class to dict if true.
    if collide:    
        if 'colliders' not in extra_data:    
            extra_data['colliders'] = {}
        if extra_data['colliders'] is None:
            extra_data['colliders'] = {}
        extra_data['colliders'][self.name] = create_collider()

    
# cloth object detect updater:
def cloth_object_update(self, context):
    """Updates the cloth object when detecting."""
    print("ran the detect updater. It did nothing.")


def manage_animation_handler(self, context):
    if self.modeling_cloth_handler_frame:
        self["modeling_cloth_handler_scene"] = False
        update_pin_group()
    
    if handler_frame in bpy.app.handlers.frame_change_post:
        bpy.app.handlers.frame_change_post.remove(handler_frame)
    
    if len(data) > 0:
        bpy.app.handlers.frame_change_post.append(handler_frame)
    
    #count = len([i for i in bpy.data.objects if i.modeling_cloth_handler_frame])

        
def manage_continuous_handler(self, context):    
    if self.modeling_cloth_handler_scene:
        self["modeling_cloth_handler_frame"] = False
        update_pin_group()
    
    if handler_scene in bpy.app.handlers.scene_update_post:
        bpy.app.handlers.scene_update_post.remove(handler_scene)
    
    if len(data) > 0:
        bpy.app.handlers.scene_update_post.append(handler_scene)
    

# =================  Handler  ======================
def handler_frame(scene):

    items = data.items()
    if len(items) == 0:
        for i in bpy.data.objects:
            i.modeling_cloth = False
        
        for i in bpy.app.handlers.frame_change_post:
            if i.__name__ == 'handler_frame':
                bpy.app.handlers.frame_change_post.remove(i)
                
        for i in bpy.app.handlers.scene_update_post:
            if i.__name__ == 'handler_scene':
                bpy.app.handlers.scene_update_post.remove(i)                
    
    for i, cloth in items:    
        if i in bpy.data.objects: # using the name. The name could change
            if cloth.ob.modeling_cloth_handler_frame:    
                run_handler(cloth)
                if cloth.ob.modeling_cloth_auto_reset:
                    if bpy.context.scene.frame_current <= 1:    
                        reset_shapes(cloth.ob)
        else:
            del(data[i])
            break


def handler_scene(scene):

    items = data.items()
    if len(items) == 0:
        for i in bpy.data.objects:
            i.modeling_cloth = False

        for i in bpy.app.handlers.frame_change_post:
            if i.__name__ == 'handler_frame':
                bpy.app.handlers.frame_change_post.remove(i)

        for i in bpy.app.handlers.scene_update_post:
            if i.__name__ == 'handler_scene':
                bpy.app.handlers.scene_update_post.remove(i)                
    
    for i, cloth in items:    
        if i in bpy.data.objects: # using the name. The name could change
            if cloth.ob.modeling_cloth_handler_scene:    
                run_handler(cloth)

        else:
            del(data[i])
            break


def pause_update(self, context):
    if not self.modeling_cloth_pause:
        update_pin_group()


def global_setup():
    global data, extra_data
    data = bpy.context.scene.modeling_cloth_data_set
    extra_data = bpy.context.scene.modeling_cloth_data_set_extra    
    extra_data['alert'] = False
    extra_data['drag_alert'] = False
    extra_data['clicked'] = False


def init_cloth(self, context):
    global data, extra_data
    data = bpy.context.scene.modeling_cloth_data_set
    extra_data = bpy.context.scene.modeling_cloth_data_set_extra
    extra_data['alert'] = False
    extra_data['drag_alert'] = False
    extra_data['last_object'] = self
    extra_data['clicked'] = False
    
    # object collisions
    colliders = [i for i in bpy.data.objects if i.modeling_cloth_object_collision]
    if len(colliders) == 0:    
        extra_data['colliders'] = None    
    
    # iterate through dict: for i, j in d.items()
    if self.modeling_cloth:
        cloth = create_instance() # generate an instance of the class
        data[cloth.name] = cloth  # store class in dictionary using the object name as a key
    
    cull = [] # can't delete dict items while iterating
    for i, value in data.items():
        if not value.ob.modeling_cloth:
            cull.append(i) # store keys to delete

    for i in cull:
        del data[i]
    
#    # could keep the handler unless there are no modeling cloth objects active
#    
#    if handler_frame in bpy.app.handlers.frame_change_post:
#        bpy.app.handlers.frame_change_post.remove(handler_frame)
#    
#    if len(data) > 0:
#        bpy.app.handlers.frame_change_post.append(handler_frame)


def main(context, event):
    # get the context arguments
    scene = context.scene
    region = context.region
    rv3d = context.region_data
    coord = event.mouse_region_x, event.mouse_region_y

    # get the ray from the viewport and mouse
    view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
    ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)

    ray_target = ray_origin + view_vector
    
    guide = create_giude()

    def visible_objects_and_duplis():
        """Loop over (object, matrix) pairs (mesh only)"""

        for obj in context.visible_objects:
            if obj.type == 'MESH':
                if obj.modeling_cloth:    
                    yield (obj, obj.matrix_world.copy())

    def obj_ray_cast(obj, matrix):
        """Wrapper for ray casting that moves the ray into object space"""

        # get the ray relative to the object
        matrix_inv = matrix.inverted()
        ray_origin_obj = matrix_inv * ray_origin
        ray_target_obj = matrix_inv * ray_target
        ray_direction_obj = ray_target_obj - ray_origin_obj

        # cast the ray
        success, location, normal, face_index = obj.ray_cast(ray_origin_obj, ray_direction_obj)

        if success:
            return location, normal, face_index
        else:
            return None, None, None

    # cast rays and find the closest object
    best_length_squared = -1.0
    best_obj = None
    for obj, matrix in visible_objects_and_duplis():
        hit, normal, face_index = obj_ray_cast(obj, matrix)
        if hit is not None:
            hit_world = matrix * hit
            vidx = [v for v in obj.data.polygons[face_index].vertices]
            verts = np.array([matrix * obj.data.shape_keys.key_blocks['modeling cloth key'].data[v].co for v in obj.data.polygons[face_index].vertices])
            vecs = verts - np.array(hit_world)
            closest = vidx[np.argmin(np.einsum('ij,ij->i', vecs, vecs))]
            length_squared = (hit_world - ray_origin).length_squared
            if best_obj is None or length_squared < best_length_squared:
                best_length_squared = length_squared
                best_obj = obj
                guide.location = matrix * obj.data.shape_keys.key_blocks['modeling cloth key'].data[closest].co
                extra_data['latest_hit'] = matrix * obj.data.shape_keys.key_blocks['modeling cloth key'].data[closest].co
                extra_data['name'] = obj.name
                extra_data['obj'] = obj
                extra_data['closest'] = closest
                
                if extra_data['just_clicked']:
                    extra_data['just_clicked'] = False
                    best_length_squared = length_squared
                    best_obj = obj
                   

# sewing --------->>>
class ModelingClothSew(bpy.types.Operator):
    """For connected two edges with sew lines"""
    bl_idname = "object.modeling_cloth_create_sew_lines"
    bl_label = "Modeling Cloth Create Sew Lines"
    bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context):
        
        #ob = get_last_object() # returns tuple with list and last cloth objects or None
        #if ob is not None:
            #obj = ob[1]
        #else:
        obj = bpy.context.object
        
        #bpy.context.scene.objects.active = obj
        mode = obj.mode
        if mode != "EDIT":
            bpy.ops.object.mode_set(mode="EDIT")
        
        create_sew_edges()
        bpy.ops.object.mode_set(mode="EDIT")            

        return {'FINISHED'}
# sewing --------->>>


class ModelingClothPin(bpy.types.Operator):
    """Modal ray cast for placing pins"""
    bl_idname = "view3d.modeling_cloth_pin"
    bl_label = "Modeling Cloth Pin"
    bl_options = {'REGISTER', 'UNDO'}
    def __init__(self):
        #bpy.ops.object.select_all(action='DESELECT')    
        extra_data['just_clicked'] = False
        
    def modal(self, context, event):
        bpy.context.window.cursor_set("CROSSHAIR")
        if event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE', 'NUMPAD_0',
        'NUMPAD_PERIOD','NUMPAD_1', 'NUMPAD_2', 'NUMPAD_3', 'NUMPAD_4',
         'NUMPAD_5', 'NUMPAD_6', 'NUMPAD_7', 'NUMPAD_8', 'NUMPAD_9'}:
            # allow navigation
            return {'PASS_THROUGH'}
        elif event.type == 'MOUSEMOVE':
            main(context, event)
            return {'RUNNING_MODAL'}
        elif event.type == 'LEFTMOUSE' and event.value == 'PRESS':
            if extra_data['latest_hit'] is not None:
                if extra_data['name'] is not None:
                    closest = extra_data['closest']
                    name = extra_data['name']
                    e = bpy.data.objects.new('modeling_cloth_pin', None)
                    bpy.context.scene.objects.link(e)
                    e.location = extra_data['latest_hit']
                    e.show_x_ray = True
                    e.select = True
                    e.empty_draw_size = .1
                    data[name].pin_list.append(closest)
                    data[name].hook_list.append(e)
                    extra_data['latest_hit'] = None
                    extra_data['name'] = None        
        
        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            delete_giude()
            cloths = [i for i in bpy.data.objects if i.modeling_cloth] # so we can select an empty and keep the settings menu up
            extra_data['alert'] = False
            if len(cloths) > 0:                                        #
                ob = extra_data['last_object']                         #
                bpy.context.scene.objects.active = ob
            bpy.context.window.cursor_set("DEFAULT")
            return {'CANCELLED'}

            
        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        cloth_objects = False
        cloths = [i for i in bpy.data.objects if i.modeling_cloth] # so we can select an empty and keep the settings menu up
        if len(cloths) > 0:
            cloth_objects = True        
            extra_data['alert'] = True
            
        if context.space_data.type == 'VIEW_3D' and cloth_objects:
            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "Active space must be a View3d")
            extra_data['alert'] = False
            bpy.context.window.cursor_set("DEFAULT")
            return {'CANCELLED'}


# drag===================================
# drag===================================
#[‘DEFAULT’, ‘NONE’, ‘WAIT’, ‘CROSSHAIR’, ‘MOVE_X’, ‘MOVE_Y’, ‘KNIFE’, ‘TEXT’, ‘PAINT_BRUSH’, ‘HAND’, ‘SCROLL_X’, ‘SCROLL_Y’, ‘SCROLL_XY’, ‘EYEDROPPER’]

def main_drag(context, event):
    # get the context arguments
    scene = context.scene
    region = context.region
    rv3d = context.region_data
    coord = event.mouse_region_x, event.mouse_region_y

    # get the ray from the viewport and mouse
    view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
    ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)

    ray_target = ray_origin + view_vector

    def visible_objects_and_duplis():
        """Loop over (object, matrix) pairs (mesh only)"""

        for obj in context.visible_objects:
            if obj.type == 'MESH':
                if obj.modeling_cloth:    
                    yield (obj, obj.matrix_world.copy())

    def obj_ray_cast(obj, matrix):
        """Wrapper for ray casting that moves the ray into object space"""

        # get the ray relative to the object
        matrix_inv = matrix.inverted()
        ray_origin_obj = matrix_inv * ray_origin
        ray_target_obj = matrix_inv * ray_target
        ray_direction_obj = ray_target_obj - ray_origin_obj
        
        # cast the ray
        success, location, normal, face_index = obj.ray_cast(ray_origin_obj, ray_direction_obj)

        if success:
            return location, normal, face_index, ray_target
        else:
            return None, None, None, ray_target

    # cast rays and find the closest object
    best_length_squared = -1.0
    best_obj = None

    for obj, matrix in visible_objects_and_duplis():
        hit, normal, face_index, target = obj_ray_cast(obj, matrix)
        extra_data['target'] = target
        if hit is not None:
            
            hit_world = matrix * hit
            length_squared = (hit_world - ray_origin).length_squared

            if best_obj is None or length_squared < best_length_squared:
                best_length_squared = length_squared
                best_obj = obj
                vidx = [v for v in obj.data.polygons[face_index].vertices]
                vert = obj.data.shape_keys.key_blocks['modeling cloth key'].data
            if best_obj is not None:    

                if extra_data['clicked']:    
                    extra_data['matrix'] = matrix.inverted()
                    data[best_obj.name].clicked = True
                    extra_data['stored_mouse'] = np.copy(target)
                    extra_data['vidx'] = vidx
                    extra_data['stored_vidx'] = np.array([vert[v].co for v in extra_data['vidx']])
                    extra_data['clicked'] = False
                    
    if extra_data['stored_mouse'] is not None:
        move = np.array(extra_data['target']) - extra_data['stored_mouse']
        extra_data['move'] = (move @ np.array(extra_data['matrix'])[:3, :3].T)
                   
                   
# dragger===
class ModelingClothDrag(bpy.types.Operator):
    """Modal ray cast for dragging"""
    bl_idname = "view3d.modeling_cloth_drag"
    bl_label = "Modeling Cloth Drag"
    bl_options = {'REGISTER', 'UNDO'}
    def __init__(self):
        #bpy.ops.object.select_all(action='DESELECT')    
        extra_data['hit'] = None
        extra_data['clicked'] = False
        extra_data['stored_mouse'] = None
        extra_data['vidx'] = None
        extra_data['new_click'] = True
        extra_data['target'] = None
        for i in data:
            data[i].clicked = False
        
    def modal(self, context, event):
        bpy.context.window.cursor_set("HAND")
        if event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}:
            # allow navigation
            return {'PASS_THROUGH'}
        elif event.type == 'MOUSEMOVE':
            #pos = queryMousePosition()            
            main_drag(context, event)
            return {'RUNNING_MODAL'}
        elif event.type == 'LEFTMOUSE' and event.value == 'PRESS':
            # when I click, If I have a hit, store the hit on press
            extra_data['clicked'] = True
            extra_data['vidx'] = []
            
        elif event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
            extra_data['clicked'] = False
            extra_data['stored_mouse'] = None
            extra_data['vidx'] = None
            extra_data['new_click'] = True
            extra_data['target'] = None
            for i in data:
                data[i].clicked = False

            
        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            extra_data['drag_alert'] = False
            extra_data['clicked'] = False
            extra_data['hit'] = None
            bpy.context.window.cursor_set("DEFAULT")
            extra_data['stored_mouse'] = None
            return {'CANCELLED'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        cloth_objects = False
        cloths = [i for i in bpy.data.objects if i.modeling_cloth] # so we can select an empty and keep the settings menu up
        if len(cloths) > 0:
            cloth_objects = True        
            extra_data['drag_alert'] = True
            
        if context.space_data.type == 'VIEW_3D' and cloth_objects:
            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "Active space must be a View3d")
            extra_data['drag_alert'] = False
            extra_data['stored_mouse'] = None
            bpy.context.window.cursor_set("DEFAULT")
            return {'CANCELLED'}


# drag===================================End
# drag===================================End



class DeletePins(bpy.types.Operator):
    """Delete modeling cloth pins and clear pin list for current object"""
    bl_idname = "object.delete_modeling_cloth_pins"
    bl_label = "Delete Modeling Cloth Pins"
    bl_options = {'REGISTER', 'UNDO'}    
    def execute(self, context):

        ob = get_last_object() # returns tuple with list and last cloth objects or None
        if ob is not None:
            l_copy = data[ob[1].name].pin_list[:]
            h_copy = data[ob[1].name].hook_list[:]
            for i in range(len(data[ob[1].name].hook_list)):
                if data[ob[1].name].hook_list[i].select:
                    bpy.data.objects.remove(data[ob[1].name].hook_list[i])
                    l_copy.remove(data[ob[1].name].pin_list[i]) 
                    h_copy.remove(data[ob[1].name].hook_list[i]) 
            
            data[ob[1].name].pin_list = l_copy
            data[ob[1].name].hook_list = h_copy

        bpy.context.scene.objects.active = ob[1]
        return {'FINISHED'}


class SelectPins(bpy.types.Operator):
    """Select modeling cloth pins for current object"""
    bl_idname = "object.select_modeling_cloth_pins"
    bl_label = "Select Modeling Cloth Pins"
    bl_options = {'REGISTER', 'UNDO'}    
    def execute(self, context):
        ob = get_last_object() # returns list and last cloth objects or None
        if ob is not None:
            #bpy.ops.object.select_all(action='DESELECT')
            for i in data[ob[1].name].hook_list:
                i.select = True

        return {'FINISHED'}


class PinSelected(bpy.types.Operator):
    """Add pins to verts selected in edit mode"""
    bl_idname = "object.modeling_cloth_pin_selected"
    bl_label = "Modeling Cloth Pin Selected"
    bl_options = {'REGISTER', 'UNDO'}    
    def execute(self, context):
        ob = bpy.context.object
        bpy.ops.object.mode_set(mode='OBJECT')
        sel = [i.index for i in ob.data.vertices if i.select]
                
        name = ob.name
        matrix = ob.matrix_world.copy()
        for v in sel:    
            e = bpy.data.objects.new('modeling_cloth_pin', None)
            bpy.context.scene.objects.link(e)
            if ob.active_shape_key is None:    
                closest = matrix * ob.data.vertices[v].co# * matrix
            else:
                closest = matrix * ob.active_shape_key.data[v].co# * matrix
            e.location = closest #* matrix
            e.show_x_ray = True
            e.select = True
            e.empty_draw_size = .1
            data[name].pin_list.append(v)
            data[name].hook_list.append(e)            
            ob.select = False
        bpy.ops.object.mode_set(mode='EDIT')       
        
        return {'FINISHED'}


class UpdataPinWeights(bpy.types.Operator):
    """Update Pin Weights"""
    bl_idname = "object.modeling_cloth_update_pin_group"
    bl_label = "Modeling Cloth Update Pin Weights"
    bl_options = {'REGISTER', 'UNDO'}        
    def execute(self, context):
        update_pin_group()
        return {'FINISHED'}


class GrowSource(bpy.types.Operator):
    """Grow Source Shape"""
    bl_idname = "object.modeling_cloth_grow"
    bl_label = "Modeling Cloth Grow"
    bl_options = {'REGISTER', 'UNDO'}        
    def execute(self, context):
        scale_source(1.02)
        return {'FINISHED'}


class ShrinkSource(bpy.types.Operator):
    """Shrink Source Shape"""
    bl_idname = "object.modeling_cloth_shrink"
    bl_label = "Modeling Cloth Shrink"
    bl_options = {'REGISTER', 'UNDO'}        
    def execute(self, context):
        scale_source(0.98)
        return {'FINISHED'}


class ResetShapes(bpy.types.Operator):
    """Reset Shapes"""
    bl_idname = "object.modeling_cloth_reset"
    bl_label = "Modeling Cloth Reset"
    bl_options = {'REGISTER', 'UNDO'}        
    def execute(self, context):
        reset_shapes()
        return {'FINISHED'}


class AddVirtualSprings(bpy.types.Operator):
    """Add Virtual Springs Between All Selected Vertices"""
    bl_idname = "object.modeling_cloth_add_virtual_spring"
    bl_label = "Modeling Cloth Add Virtual Spring"
    bl_options = {'REGISTER', 'UNDO'}        
    def execute(self, context):
        add_virtual_springs()
        return {'FINISHED'}


class RemoveVirtualSprings(bpy.types.Operator):
    """Remove Virtual Springs Between All Selected Vertices"""
    bl_idname = "object.modeling_cloth_remove_virtual_spring"
    bl_label = "Modeling Cloth Remove Virtual Spring"
    bl_options = {'REGISTER', 'UNDO'}        
    def execute(self, context):
        add_virtual_springs(remove=True)
        return {'FINISHED'}


class ApplyClothToMesh(bpy.types.Operator):
    """Apply cloth effects to mesh for export."""
    bl_idname = "object.modeling_cloth_apply_cloth_to_mesh"
    bl_label = "Modeling Cloth Remove Virtual Spring"
    bl_options = {'REGISTER', 'UNDO'}        
    def execute(self, context):
        ob = get_last_object()[1]
        v_count = len(ob.data.vertices)
        co = np.zeros(v_count * 3, dtype=np.float32)
        ob.data.shape_keys.key_blocks['modeling cloth key'].data.foreach_get('co', co)
        ob.data.shape_keys.key_blocks['Basis'].data.foreach_set('co', co)
        ob.data.shape_keys.key_blocks['Basis'].mute = True
        ob.data.shape_keys.key_blocks['Basis'].mute = False
        ob.data.vertices.foreach_set('co', co)
        ob.data.update()

        return {'FINISHED'}


def create_properties():            

    bpy.types.Object.modeling_cloth = bpy.props.BoolProperty(name="Modeling Cloth", 
        description="For toggling modeling cloth", 
        default=False, update=init_cloth)

    bpy.types.Object.modeling_cloth_floor = bpy.props.BoolProperty(name="Modeling Cloth Floor", 
        description="Stop at floor", 
        default=False)

    bpy.types.Object.modeling_cloth_pause = bpy.props.BoolProperty(name="Modeling Cloth Pause", 
        description="Stop without removing data",
        default=True, update=pause_update)
    
    # handler type ----->>>        
    bpy.types.Object.modeling_cloth_handler_scene = bpy.props.BoolProperty(name="Modeling Cloth Continuous Update", 
        description="Choose continuous update", 
        default=False, update=manage_continuous_handler)        

    bpy.types.Object.modeling_cloth_handler_frame = bpy.props.BoolProperty(name="Modeling Cloth Handler Animation Update", 
        description="Choose animation update", 
        default=False, update=manage_animation_handler)
        
    bpy.types.Object.modeling_cloth_auto_reset = bpy.props.BoolProperty(name="Modeling Cloth Reset at Frame 1", 
        description="Automatically reset if the current frame number is 1 or less", 
        default=False)#, update=manage_handlers)        
    # ------------------>>>

    bpy.types.Object.modeling_cloth_noise = bpy.props.FloatProperty(name="Modeling Cloth Noise", 
        description="Set the noise strength", 
        default=0.001, precision=4, min=0, max=1, update=refresh_noise)

    bpy.types.Object.modeling_cloth_noise_decay = bpy.props.FloatProperty(name="Modeling Cloth Noise Decay", 
        description="Multiply the noise by this value each iteration", 
        default=0.99, precision=4, min=0, max=1)#, update=refresh_noise_decay)

    # spring forces ------------>>>
    bpy.types.Object.modeling_cloth_spring_force = bpy.props.FloatProperty(name="Modeling Cloth Spring Force", 
        description="Set the spring force", 
        default=1.0, precision=4, min=0, max=2.5)#, update=refresh_noise)

    bpy.types.Object.modeling_cloth_push_springs = bpy.props.FloatProperty(name="Modeling Cloth Push Spring Force", 
        description="Set the push spring force", 
        default=1.0, precision=4, min=0, max=2.5)#, update=refresh_noise)
    
    # bend springs
    bpy.types.Object.modeling_cloth_bend_stiff = bpy.props.FloatProperty(name="Modeling Cloth Bend Spring Force", 
        description="Set the bend spring force", 
        default=0.0, precision=4, min=0, max=10, soft_max=1)#, update=refresh_noise)
    # -------------------------->>>

    bpy.types.Object.modeling_cloth_gravity = bpy.props.FloatProperty(name="Modeling Cloth Gravity", 
        description="Modeling cloth gravity", 
        default=0.0, precision=4, soft_min=-10, soft_max=10, min=-1000, max=1000)

    bpy.types.Object.modeling_cloth_iterations = bpy.props.IntProperty(name="Stiffness", 
        description="How stiff the cloth is", 
        default=2, min=1, max=500)#, update=refresh_noise_decay)

    bpy.types.Object.modeling_cloth_velocity = bpy.props.FloatProperty(name="Velocity", 
        description="Cloth keeps moving", 
        default=.98, min= -200, max=200, soft_min= -1, soft_max=1)#, update=refresh_noise_decay)

    # Wind. Note, wind should be measured against normal and be at zero when normals are at zero. Squared should work
    bpy.types.Object.modeling_cloth_wind_x = bpy.props.FloatProperty(name="Wind X", 
        description="Not the window cleaner", 
        default=0, min= -10, max=10, soft_min= -1, soft_max=1)#, update=refresh_noise_decay)

    bpy.types.Object.modeling_cloth_wind_y = bpy.props.FloatProperty(name="Wind Y", 
        description="Y? Because wind is cool", 
        default=0, min= -10, max=10, soft_min= -1, soft_max=1)#, update=refresh_noise_decay)

    bpy.types.Object.modeling_cloth_wind_z = bpy.props.FloatProperty(name="Wind Z", 
        description="It's windzee outzide", 
        default=0, min= -10, max=10, soft_min= -1, soft_max=1)#, update=refresh_noise_decay)

    bpy.types.Object.modeling_cloth_turbulence = bpy.props.FloatProperty(name="Wind Turbulence", 
        description="Add Randomness to wind", 
        default=0, min=0, max=10, soft_min= 0, soft_max=1)#, update=refresh_noise_decay)

    # self collision ----->>>
    bpy.types.Object.modeling_cloth_self_collision = bpy.props.BoolProperty(name="Modeling Cloth Self Collsion", 
        description="Toggle self collision", 
        default=False, update=collision_data_update)

#    bpy.types.Object.modeling_cloth_self_collision_force = bpy.props.FloatProperty(name="recovery force", 
#        description="Self colide faces repel", 
#        default=.17, precision=4, min= -1.1, max=1.1, soft_min= 0, soft_max=1)

    bpy.types.Object.modeling_cloth_self_collision_margin = bpy.props.FloatProperty(name="Margin", 
        description="Self colide faces margin", 
        default=.08, precision=4, min= -1, max=1, soft_min= 0, soft_max=1)

#    bpy.types.Object.modeling_cloth_self_collision_cy_size = bpy.props.FloatProperty(name="Cylinder size", 
#        description="Self colide faces cylinder size", 
#        default=1, precision=4, min= 0, max=4, soft_min= 0, soft_max=1.5)
    # ---------------------->>>

    # extras ------->>>
    bpy.types.Object.modeling_cloth_inflate = bpy.props.FloatProperty(name="inflate", 
        description="add force to vertex normals", 
        default=0, precision=4, min= -10, max=10, soft_min= -1, soft_max=1)

    bpy.types.Object.modeling_cloth_sew = bpy.props.FloatProperty(name="sew", 
        description="add force to vertex normals", 
        default=0, precision=4, min= -10, max=10, soft_min= -1, soft_max=1)
    # -------------->>>

    # external collisions ------->>>
    bpy.types.Object.modeling_cloth_object_collision = bpy.props.BoolProperty(name="Modeling Cloth Self Collsion", 
        description="Detect and collide with this object", 
        default=False, update=collision_object_update)

    #bpy.types.Object.modeling_cloth_collision_animated = bpy.props.BoolProperty(name="Modeling Cloth Collsion Animated", 
        #description="Treat collide object as animated. (turn off for speed on static objects)", 
        #default=True)#, update=collision_object_update)
    
    bpy.types.Object.modeling_cloth_object_detect = bpy.props.BoolProperty(name="Modeling Cloth Self Collsion", 
        description="Detect collision objects", 
        default=True, update=cloth_object_update)    

    bpy.types.Object.modeling_cloth_outer_margin = bpy.props.FloatProperty(name="Modeling Cloth Outer Margin", 
        description="Collision margin on positive normal side of face", 
        default=0.04, precision=4, min=0, max=100, soft_min=0, soft_max=1000)
        
    bpy.types.Object.modeling_cloth_inner_margin = bpy.props.FloatProperty(name="Modeling Cloth Inner Margin", 
        description="Collision margin on negative normal side of face", 
        default=0.08, precision=4, min=0, max=100, soft_min=0, soft_max=1000)        
    # ---------------------------->>>
    
    # more collision stuff ------->>>
    bpy.types.Object.modeling_cloth_grid_size = bpy.props.IntProperty(name="Modeling Cloth Grid Size", 
    description="Max subdivisions for the dynamic broad phase grid", 
    default=10, min=0, max=1000, soft_min=0, soft_max=1000)
    
    # property dictionaries
    if "modeling_cloth_data_set" not in dir(bpy.types.Scene):
        bpy.types.Scene.modeling_cloth_data_set = {} 
        bpy.types.Scene.modeling_cloth_data_set_extra = {} 
    
        
def remove_properties():            
    '''Drives to the grocery store and buys a sandwich'''
    del(bpy.types.Object.modeling_cloth)
    del(bpy.types.Object.modeling_cloth_floor)
    del(bpy.types.Object.modeling_cloth_pause)
    del(bpy.types.Object.modeling_cloth_noise)    
    del(bpy.types.Object.modeling_cloth_noise_decay)
    del(bpy.types.Object.modeling_cloth_spring_force)
    del(bpy.types.Object.modeling_cloth_gravity)        
    del(bpy.types.Object.modeling_cloth_iterations)
    del(bpy.types.Object.modeling_cloth_velocity)
    del(bpy.types.Object.modeling_cloth_inflate)
    del(bpy.types.Object.modeling_cloth_sew)

    # self collision
#    del(bpy.types.Object.modeling_cloth_self_collision)    
#    del(bpy.types.Object.modeling_cloth_self_collision_cy_size)    
#    del(bpy.types.Object.modeling_cloth_self_collision_force)    
#    del(bpy.types.Object.modeling_cloth_self_collision_margin)    

    # data storage
    del(bpy.types.Scene.modeling_cloth_data_set)
    del(bpy.types.Scene.modeling_cloth_data_set_extra)


class ModelingClothPanel(bpy.types.Panel):
    """Modeling Cloth Panel"""
    bl_label = "Modeling Cloth Panel"
    bl_idname = "Modeling Cloth"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS'
    bl_category = "Extended Tools"
    #gt_show = True
    
    def draw(self, context):
        status = False
        layout = self.layout
        
        # tools
        col = layout.column(align=True)
        col.label(text="Tools")        
        col.operator("object.modeling_cloth_create_sew_lines", text="Sew Lines", icon="MOD_UVPROJECT")
        col.operator("object.modeling_cloth_apply_cloth_to_mesh", text="Apply to Mesh", icon="FILE_TICK")
        
        # modeling cloth
        col = layout.column(align=True)
        col.label(text="Modeling Cloth")
        ob = bpy.context.object
        cloths = [i for i in bpy.data.objects if i.modeling_cloth] # so we can select an empty and keep the settings menu up
        if len(cloths) > 0:
            extra_data = bpy.context.scene.modeling_cloth_data_set_extra
            if 'alert' not in extra_data:
                global_setup()    
            status = extra_data['alert']
            if ob is not None:
                if ob.type != 'MESH' or status:
                    ob = extra_data['last_object']

        if ob is not None:
            if ob.type == 'MESH':
                col.prop(ob ,"modeling_cloth", text="Modeling Cloth", icon='SURFACE_DATA')               
                if ob.modeling_cloth:    
                    col.prop(ob ,"modeling_cloth_self_collision", text="Self Collision", icon='PHYSICS')
                    col.prop(ob ,"modeling_cloth_self_collision_margin", text="Self Margin")#, icon='PLAY')
                    
                pause = 'PAUSE'
                if ob.modeling_cloth_pause:
                    pause = 'PLAY'
                
                col.prop(ob ,"modeling_cloth_object_collision", text="Collider", icon="STYLUS_PRESSURE")
                #if ob.modeling_cloth_object_collision:
                    #col.prop(ob ,"modeling_cloth_collision_animated", text="Animated", icon="POSE_DATA")
                if ob.modeling_cloth_object_collision:    
                    col.prop(ob ,"modeling_cloth_outer_margin", text="Outer Margin", icon="FORCE_FORCE")
                    col.prop(ob ,"modeling_cloth_inner_margin", text="Inner Margin", icon="STICKY_UVS_LOC")
                    col = layout.column(align=True)
                    
                col.label("Collide List:")
                colliders = [i.name for i in bpy.data.objects if i.modeling_cloth_object_collision]
                for i in colliders:
                    col.label(i)

                if ob.modeling_cloth:

                    # object collisions
                    col = layout.column(align=True)
                    col.label("Collisions")
                    if ob.modeling_cloth:    
                        col.prop(ob ,"modeling_cloth_object_detect", text="Object Collisions", icon="PHYSICS")

                    col = layout.column(align=True)
                    col.scale_y = 2.0
                    #col.prop(ob ,"modeling_cloth_pause", text=pause, icon=pause)               

                    col = layout.column(align=True)
                    col.scale_y = 1.4
                    col.prop(ob, "modeling_cloth_grid_size", text="Grid Boxes", icon="MESH_GRID")
                    col.prop(ob, "modeling_cloth_handler_frame", text="Animation Update", icon="TRIA_RIGHT")
                    if ob.modeling_cloth_handler_frame:    
                        col.prop(ob, "modeling_cloth_auto_reset", text="Frame 1 Reset")
                    col.prop(ob, "modeling_cloth_handler_scene", text="Continuous Update", icon="TIME")
                    col = layout.column(align=True)
                    col.scale_y = 2.0
                    col.operator("object.modeling_cloth_reset", text="Reset")
                    col.alert = extra_data['drag_alert']
                    col.operator("view3d.modeling_cloth_drag", text="Grab")
                    col = layout.column(align=True)
                        
                    col.prop(ob ,"modeling_cloth_iterations", text="Iterations")#, icon='OUTLINER_OB_LATTICE')               
                    col.prop(ob ,"modeling_cloth_spring_force", text="Stiffness")#, icon='OUTLINER_OB_LATTICE')               
                    col.prop(ob ,"modeling_cloth_push_springs", text="Push Springs")#, icon='OUTLINER_OB_LATTICE')               
                    col.prop(ob ,"modeling_cloth_bend_stiff", text="Bend Springs")#, icon='CURVE_NCURVE')               
                    col.prop(ob ,"modeling_cloth_noise", text="Noise")#, icon='PLAY')               
                    col.prop(ob ,"modeling_cloth_noise_decay", text="Decay Noise")#, icon='PLAY')               
                    col.prop(ob ,"modeling_cloth_gravity", text="Gravity")#, icon='PLAY')        
                    col.prop(ob ,"modeling_cloth_inflate", text="Inflate")#, icon='PLAY')        
                    col.prop(ob ,"modeling_cloth_sew", text="Sew Force")#, icon='PLAY')        
                    col.prop(ob ,"modeling_cloth_velocity", text="Velocity")#, icon='PLAY')        
                    col = layout.column(align=True)
                    col.label("Wind")                
                    col.prop(ob ,"modeling_cloth_wind_x", text="Wind X")#, icon='PLAY')        
                    col.prop(ob ,"modeling_cloth_wind_y", text="Wind Y")#, icon='PLAY')        
                    col.prop(ob ,"modeling_cloth_wind_z", text="Wind Z")#, icon='PLAY')        
                    col.prop(ob ,"modeling_cloth_turbulence", text="Turbulence")#, icon='PLAY')        
                    col.prop(ob ,"modeling_cloth_floor", text="Floor")#, icon='PLAY')        
                    col = layout.column(align=True)
                    col.scale_y = 1.5
                    col.alert = status
                    if ob.modeling_cloth:    
                        if ob.mode == 'EDIT':
                            col.operator("object.modeling_cloth_pin_selected", text="Pin Selected")
                            col = layout.column(align=True)
                            col.operator("object.modeling_cloth_add_virtual_spring", text="Add Virtual Springs")
                            col.operator("object.modeling_cloth_remove_virtual_spring", text="Remove Selected")
                        else:
                            col.operator("view3d.modeling_cloth_pin", text="Create Pins")
                        col = layout.column(align=True)
                        col.operator("object.select_modeling_cloth_pins", text="Select Pins")
                        col.operator("object.delete_modeling_cloth_pins", text="Delete Pins")
                        col.operator("object.modeling_cloth_grow", text="Grow Source")
                        col.operator("object.modeling_cloth_shrink", text="Shrink Source")
                        col = layout.column(align=True)
                        #col.prop(ob ,"modeling_cloth_self_collision", text="Self Collision")#, icon='PLAY')        
                        #col.prop(ob ,"modeling_cloth_self_collision_force", text="Repel")#, icon='PLAY')        
                        #col.prop(ob ,"modeling_cloth_self_collision_margin", text="Margin")#, icon='PLAY')        
                        #col.prop(ob ,"modeling_cloth_self_collision_cy_size", text="Cylinder Size")#, icon='PLAY')        

                    
                # =============================
                col = layout.column(align=True)
                col.label('Collision Series')
                col.operator("object.modeling_cloth_collision_series", text="Paperback")
                col.operator("object.modeling_cloth_collision_series_kindle", text="Kindle")
                col.operator("object.modeling_cloth_donate", text="Donate")


class CollisionSeries(bpy.types.Operator):
    """Support my addons by checking out my awesome sci fi books"""
    bl_idname = "object.modeling_cloth_collision_series"
    bl_label = "Modeling Cloth Collision Series"
        
    def execute(self, context):
        collision_series()
        return {'FINISHED'}


class CollisionSeriesKindle(bpy.types.Operator):
    """Support my addons by checking out my awesome sci fi books"""
    bl_idname = "object.modeling_cloth_collision_series_kindle"
    bl_label = "Modeling Cloth Collision Series Kindle"
        
    def execute(self, context):
        collision_series(False)
        return {'FINISHED'}


class Donate(bpy.types.Operator):
    """Support my addons by donating"""
    bl_idname = "object.modeling_cloth_donate"
    bl_label = "Modeling Cloth Donate"

        
    def execute(self, context):
        collision_series(False, False)
        self.report({'INFO'}, 'Paypal, The3dAdvantage@gmail.com')
        return {'FINISHED'}


def collision_series(paperback=True, kindle=True):
    import webbrowser
    import imp
    if paperback:    
        webbrowser.open("https://www.createspace.com/6043857")
        imp.reload(webbrowser)
        webbrowser.open("https://www.createspace.com/7164863")
        return
    if kindle:
        webbrowser.open("https://www.amazon.com/Resolve-Immortal-Flesh-Collision-Book-ebook/dp/B01CO3MBVQ")
        imp.reload(webbrowser)
        webbrowser.open("https://www.amazon.com/Formulacrum-Collision-Book-Rich-Colburn-ebook/dp/B0711P744G")
        return
    webbrowser.open("https://www.paypal.com/donate/?token=G1UymFn4CP8lSFn1r63jf_XOHAuSBfQJWFj9xjW9kWCScqkfYUCdTzP-ywiHIxHxYe7uJW&country.x=US&locale.x=US")

# ============================================================================================    
    


def register():
    create_properties()
    bpy.utils.register_class(ModelingClothPanel)
    bpy.utils.register_class(ModelingClothPin)
    bpy.utils.register_class(ModelingClothDrag)
    bpy.utils.register_class(DeletePins)
    bpy.utils.register_class(SelectPins)
    bpy.utils.register_class(PinSelected)
    bpy.utils.register_class(GrowSource)
    bpy.utils.register_class(ShrinkSource)
    bpy.utils.register_class(ResetShapes)
    bpy.utils.register_class(UpdataPinWeights)
    bpy.utils.register_class(AddVirtualSprings)
    bpy.utils.register_class(RemoveVirtualSprings)
    bpy.utils.register_class(ModelingClothSew)
    bpy.utils.register_class(ApplyClothToMesh)
    
    
    bpy.utils.register_class(CollisionSeries)
    bpy.utils.register_class(CollisionSeriesKindle)
    bpy.utils.register_class(Donate)


def unregister():
    remove_properties()
    bpy.utils.unregister_class(ModelingClothPanel)
    bpy.utils.unregister_class(ModelingClothPin)
    bpy.utils.unregister_class(ModelingClothDrag)
    bpy.utils.unregister_class(DeletePins)
    bpy.utils.unregister_class(SelectPins)
    bpy.utils.unregister_class(PinSelected)
    bpy.utils.unregister_class(GrowSource)
    bpy.utils.unregister_class(ShrinkSource)
    bpy.utils.unregister_class(ResetShapes)
    bpy.utils.unregister_class(UpdataPinWeights)
    bpy.utils.unregister_class(AddVirtualSprings)
    bpy.utils.unregister_class(RemoveVirtualSprings)
    bpy.utils.unregister_class(ModelingClothSew)
    bpy.utils.unregister_class(ApplyClothToMesh)
    
    
    bpy.utils.unregister_class(CollisionSeries)
    bpy.utils.unregister_class(CollisionSeriesKindle)
    bpy.utils.unregister_class(Donate)
    
    
if __name__ == "__main__":
    register()

    # testing!!!!!!!!!!!!!!!!
    #generate_collision_data(bpy.context.object)
    # testing!!!!!!!!!!!!!!!!
    
    for i in bpy.data.objects:
        i.modeling_cloth = False
        i.modeling_cloth_object_collision = False
        
    for i in bpy.app.handlers.frame_change_post:
        if i.__name__ == 'handler_frame':
            bpy.app.handlers.frame_change_post.remove(i)
            
    for i in bpy.app.handlers.scene_update_post:
        if i.__name__ == 'handler_scene':
            bpy.app.handlers.scene_update_post.remove(i)