# #####BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# #####END GPL LICENSE BLOCK #####

bl_info = {
    "name": "Curve Tools",
    "author": "Zak",
    "version": (0, 1, 5),
    "blender": (2, 59, 0),
    "location": "Properties > Object data",
    "description": "Creates driven Lofts or Birails between curves",
    "warning": "may be buggy or incomplete",
    "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
        "Scripts/Curve/Curve_Tools",
    "tracker_url": "https://developer.blender.org/T27720",
    "category": "Add Curve"}

### UPDATES
#1.5

#-Fixed birail function
#-Added Curve Snap to W key specials menu.
#-Removed some functions that arent needed and wrapped into the operators.
#-nurbs with weights for loft and birail
#-Panel Moved to view 3d tools
#-inserted TODO comments
#-tried to implement live tension and bias for Hermite interpolation by driving the mesh but
#i dont know why, the code is executed all the time even if you dont change the variables.
#-snap to curves affects all the curves on the scene
#-i was able to preserve handle types when split or subdivide


#1.4
#-incorporate curve snap
#assign a copy transform to helper
#-nurbs implemented (in progress)

import bpy
from mathutils import *
from bpy.props import *

print("----------")


### PROPERTIES
class sprops(bpy.types.PropertyGroup):
    pass


bpy.utils.register_class(sprops)

#bpy.selection will store objects names in the order they were selected
bpy.selection=[]


#dodriver a simple checker to chosse whether  you want a driven mesh or not.
bpy.types.Scene.dodriver = BoolProperty(name = "dodriver",                                      default=False)

#interpolation types
myitems = (('0','Linear', ''),('1','Cubic',''),('2','Catmull',''), ('3','Hermite',''))
bpy.types.Scene.intype = EnumProperty(name="intype", items = myitems, default='3')

#number of steps and spans to be created
bpy.types.Scene.steps = IntProperty(name="steps", default=12, min=2)
bpy.types.Scene.spans = IntProperty(name="spans", default=12, min=2)

#parameters for Hermite interpolation
bpy.types.Scene.tension = FloatProperty(name = "tension", min=0.0, default=0.0)
bpy.types.Scene.bias = FloatProperty(name = "bias", min=0.0, default = 0.5)

#proportional birail
bpy.types.Scene.proportional = BoolProperty(name="proportional", default=False)

#this stores the result of calculating the curve length
bpy.types.Scene.clen = FloatProperty(name="clen", default=0.0, precision=5)

#minimun distance for merge curve tool
bpy.types.Scene.limit = FloatProperty(name="limit", default=0.1, precision=3)


### SELECT BY ORDER BLOCK

#i dont know what to do with this. Im not using it yet.
def selected_points(curve):

    selp = []
    for spl in curve.splines:
        if spl.type=="BEZIER":
            points = spl.bezier_points
            for p in points:
                if p.select_control_point:
                    selp.append(p)

        elif spl.type=="NURBS":
            points = spl.points
            for p in points:
                if p.select:
                    selp.append(p)
    return selp

#writes bpy.selection when a new object is selected or deselected
#it compares bpy.selection with bpy.context.selected_objects

def select():

    #print(bpy.context.mode)
    if bpy.context.mode=="OBJECT":
        obj = bpy.context.object
        sel = len(bpy.context.selected_objects)

        if sel==0:
            bpy.selection=[]
        else:
            if sel==1:
                bpy.selection=[]
                bpy.selection.append(obj)
            elif sel>len(bpy.selection):
                for sobj in bpy.context.selected_objects:
                    if (sobj in bpy.selection)==False:
                        bpy.selection.append(sobj)

            elif sel<len(bpy.selection):
                for it in bpy.selection:
                    if (it in bpy.context.selected_objects)==False:
                        bpy.selection.remove(it)

    #on edit mode doesnt work well


#executes selection by order at 3d view
class Selection(bpy.types.Header):
    bl_label = "Selection"
    bl_space_type = "VIEW_3D"

    def __init__(self):
        #print("hey")
        select()

    def draw(self, context):
        layout = self.layout
        row = layout.row()
        row.label("Sel: "+str(len(bpy.selection)))

### GENERAL CURVE FUNCTIONS

#distance between 2 points
def dist(p1, p2):
    return (p2-p1).magnitude

#sets cursors position for debugging porpuses
def cursor(pos):
    bpy.context.scene.cursor_location = pos

#cuadratic bezier value
def quad(p, t):
    return p[0]*(1.0-t)**2.0 + 2.0*t*p[1]*(1.0-t) + p[2]*t**2.0

#cubic bezier value
def cubic(p, t):
    return p[0]*(1.0-t)**3.0 + 3.0*p[1]*t*(1.0-t)**2.0 + 3.0*p[2]*(t**2.0)*(1.0-t) + p[3]*t**3.0

#gets a bezier segment's control points on global coordinates
def getbezpoints(spl, mt, seg=0):
    points = spl.bezier_points
    p0 = mt * points[seg].co
    p1 = mt * points[seg].handle_right
    p2 = mt * points[seg+1].handle_left
    p3 = mt * points[seg+1].co
    return p0, p1, p2, p3

#gets nurbs polygon control points on global coordinates
def getnurbspoints(spl, mw):
    pts = []
    ws = []
    for p in spl.points:
        v = Vector(p.co[0:3])*mw
        pts.append(v)
        ws.append(p.weight)
    return pts , ws

#calcs a nurbs knot vector
def knots(n, order, type=0):#0 uniform 1 endpoints 2 bezier

    kv = []

    t = n+order
    if type==0:
        for i in range(0, t):
            kv.append(1.0*i)

    elif type==1:
        k=0.0
        for i in range(1, t+1):
            kv.append(k)
            if i>=order and i<=n:
                k+=1.0
    elif type==2:
        if order==4:
            k=0.34
            for a in range(0,t):
                if a>=order and a<=n: k+=0.5
                kv.append(floor(k))
                k+=1.0/3.0

        elif order==3:
            k=0.6
            for a in range(0, t):
                if a >=order and a<=n: k+=0.5
                kv.append(floor(k))

    ##normalize the knot vector
    for i in range(0, len(kv)):
        kv[i]=kv[i]/kv[-1]

    return kv

#nurbs curve evaluation
def C(t, order, points, weights, knots):
    #c = Point([0,0,0])
    c = Vector()
    rational = 0
    i = 0
    while i < len(points):
        b = B(i, order, t, knots)
        p = points[i] * (b * weights[i])
        c = c + p
        rational = rational + b*weights[i]
        i = i + 1

    return c * (1.0/rational)

#nurbs basis function
def B(i,k,t,knots):
    ret = 0
    if k>0:
        n1 = (t-knots[i])*B(i,k-1,t,knots)
        d1 = knots[i+k] - knots[i]
        n2 = (knots[i+k+1] - t) * B(i+1,k-1,t,knots)
        d2 = knots[i+k+1] - knots[i+1]
        if d1 > 0.0001 or d1 < -0.0001:
            a = n1 / d1
        else:
            a = 0
        if d2 > 0.0001 or d2 < -0.0001:
            b = n2 / d2
        else:
            b = 0
        ret = a + b
        #print "B i = %d, k = %d, ret = %g, a = %g, b = %g\n"%(i,k,ret,a,b)
    else:
        if knots[i] <= t and t <= knots[i+1]:
            ret = 1
        else:
            ret = 0
    return ret

#calculates a global parameter t along all control points
#t=0 begining of the curve
#t=1 ending of the curve

def calct(obj, t):

    spl=None
    mw = obj.matrix_world
    if obj.data.splines.active==None:
        if len(obj.data.splines)>0:
            spl=obj.data.splines[0]
    else:
        spl = obj.data.splines.active

    if spl==None:
        return False

    if spl.type=="BEZIER":
        points = spl.bezier_points
        nsegs = len(points)-1

        d = 1.0/nsegs
        seg = int(t/d)
        t1 = t/d - int(t/d)

        if t==1:
            seg-=1
            t1 = 1.0

        p = getbezpoints(spl,mw, seg)

        coord = cubic(p, t1)

        return coord

    elif spl.type=="NURBS":
        data = getnurbspoints(spl, mw)
        pts = data[0]
        ws = data[1]
        order = spl.order_u
        n = len(pts)
        ctype = spl.use_endpoint_u
        kv = knots(n, order, ctype)

        coord = C(t, order-1, pts, ws, kv)

        return coord

#length of the curve
def arclength(objs):
    length = 0.0

    for obj in objs:
        if obj.type=="CURVE":
            prec = 1000 #precision
            inc = 1/prec #increments

            ### TODO: set a custom precision value depending the number of curve points
            #that way it can gain on accuracy in less operations.

            #subdivide the curve in 1000 lines and sum its magnitudes
            for i in range(0, prec):
                ti = i*inc
                tf = (i+1)*inc
                a = calct(obj, ti)
                b = calct(obj, tf)
                r = (b-a).magnitude
                length+=r

    return length


class ArcLengthOperator(bpy.types.Operator):

    bl_idname = "curve.arc_length_operator"
    bl_label = "Measures the length of a curve"

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

    def execute(self, context):
        objs = context.selected_objects
        context.scene.clen = arclength(objs)
        return {'FINISHED'}

### LOFT INTERPOLATIONS

#objs = selected objects
#i = object index
#t = parameter along u direction
#tr = parameter along v direction

#linear
def intl(objs, i, t, tr):
    p1 = calct(objs[i],t)
    p2 = calct(objs[i+1], t)

    r = p1 + (p2 - p1)*tr

    return r

#tipo = interpolation type
#tension and bias are for hermite interpolation
#they can be changed to obtain different lofts.

#cubic
def intc(objs, i, t, tr, tipo=3, tension=0.0, bias=0.0):

    ncurves =len(objs)

    #if 2 curves go to linear interpolation regardless the one you choose
    if ncurves<3:
        return intl(objs, i, t, tr)
    else:

        #calculates the points to be interpolated on each curve
        if i==0:
            p0 = calct(objs[i], t)
            p1 = p0
            p2 = calct(objs[i+1], t)
            p3 = calct(objs[i+2], t)
        else:
            if ncurves-2 == i:
                p0 = calct(objs[i-1], t)
                p1 = calct(objs[i], t)
                p2 = calct(objs[i+1], t)
                p3 = p2
            else:
                p0 = calct(objs[i-1], t)
                p1 = calct(objs[i], t)
                p2 = calct(objs[i+1], t)
                p3 = calct(objs[i+2], t)


    #calculates the interpolation between those points
    #i used methods from this page: http://paulbourke.net/miscellaneous/interpolation/

    if tipo==0:
        #linear
        return intl(objs, i, t, tr)
    elif tipo == 1:
        #natural cubic
        t2 = tr*tr
        a0 = p3-p2-p0+p1
        a1 = p0-p1-a0
        a2 = p2-p0
        a3 = p1
        return a0*tr*t2 + a1*t2+a2*tr+a3
    elif tipo == 2:
        #catmull it seems to be working. ill leave it for now.
        t2 = tr*tr
        a0 = -0.5*p0 +1.5*p1 -1.5*p2 +0.5*p3
        a1 = p0 - 2.5*p1 + 2*p2 -0.5*p3
        a2 = -0.5*p0 + 0.5 *p2
        a3 = p1
        return a0*tr*tr + a1*t2+a2*tr+a3

    elif tipo == 3:
        #hermite
        tr2 = tr*tr
        tr3 = tr2*tr
        m0 = (p1-p0)*(1+bias)*(1-tension)/2
        m0+= (p2-p1)*(1-bias)*(1-tension)/2
        m1 = (p2-p1)*(1+bias)*(1-tension)/2
        m1+= (p3-p2)*(1-bias)*(1-tension)/2
        a0 = 2*tr3 - 3*tr2 + 1
        a1 = tr3 - 2 * tr2+ tr
        a2 = tr3 - tr2
        a3 = -2*tr3 + 3*tr2

        return a0*p1+a1*m0+a2*m1+a3*p2


#handles loft driver expression
#example: loftdriver('Loft', 'BezierCurve;BezierCurve.001;BezierCurve.002', 3)

#name: its the name of the mesh to be driven
#objs: the  names of the curves that drives the mesh
#3 interpolation type

def loftdriver(name, objs, intype):
    #print("ejecutando "+name)
    intype = int(intype)

    tension = 0.0
    bias = 0.5
    #if the loft object still exists proceed normal
    try:
        resobj = bpy.data.objects[name]
        spans = resobj["spans"]
        steps = resobj["steps"]
        if intype==3: #hermite
            tension = resobj['tension']
            bias = resobj['bias']

    #if not delete the driver
    except:
        curve = bpy.context.object
        for it in curve.keys():
            if it == "driver":
                curve.driver_remove('["driver"]')
        return False

    objs = objs.split(";")
    #objs = objs[0:-1]


    #retrieves the curves from the objs string
    for i, l in enumerate(objs):
        objs[i] = bpy.data.objects[l]



    #calcs the new vertices coordinates if we change the curves.
    vxs = loft(objs, steps, spans, intype, tension, bias)

    #apply the new cordinates to the loft object
    me = resobj.data

    for i in range(0, len(me.vertices)):
        me.vertices[i].co = vxs[i]
    me.update()
    return spans

#NOTES:
#loftdriver function will fail or produce weird results if:
#the user changes resobj["spans"] or resobj["steps"]
#if we delete any vertex from the loft object

### TODO:check if thats the case to remove the drivers

#creates the drivers expressions for each curve
def createloftdriver(objs, res, intype):

    line = ""
    for obj in objs:
        line+=obj.name+";"
    line=line[0:-1]
    name = res.name

    interp = str(intype)

    for obj in objs:
        obj["driver"] = 1.0

        obj.driver_add('["driver"]')
        obj.animation_data.drivers[0].driver.expression = "loftdriver('"+ name +"', '" + line + "', "+interp+")"


    ### creating this driver will execute loft all the time without reason,
    #and if i cant drive the mesh i cannot implement live tension and bias

#   res['driver'] = 1.0
#   if res.animation_data==None:
#       res.animation_data_create()
#   res.driver_add('["driver"]')
#   res.animation_data.drivers[0].driver.expression = "loftdriver('"+ name +"', '" + line + "', "+interp+")"

#calculates the vertices position of the loft object
def loft(objs, steps, spans, interpolation=1, tension=0.0, bias=0.5):
    verts=[]

    for i in range(0, len(objs)):

        for j in range(0,steps+1):
            t = 1.0*j/steps
            verts.append(calct(objs[i], t))

        temp2=[]
        if i<len(objs)-1:
            for l in range(1, spans):
                tr = 1.0*l/spans
                for k in range(0, steps+1):
                    t=1.0*k/steps
                    if interpolation:
                        pos = intc(objs, i, t, tr, interpolation, tension, bias)
                    else:
                        pos = intl(objs,i, t, tr)

                    temp2.append(pos)
            verts.extend(temp2)
    return verts


#loft operator

class LoftOperator(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "mesh.loft_operator"
    bl_label = "Loft between bezier curves"

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

    def execute(self, context):
        #retrieves the curves in the order they were selected
        objs = bpy.selection

        spans = context.scene.spans
        steps = context.scene.steps

        intype = int(context.scene.intype)

        verts = loft(objs, steps, spans, intype)

        nfaces = steps*spans*(len(objs)-1)
        faces=[]
        for i in range(0, nfaces):
            d = int(i/steps)
            f = [i+d,i+d+1, i+d+steps+2, i+d+steps+1]
            #inverts normals
            #f = [i+d,i+d+steps+1, i+d+steps+2, i+d+1]
            faces.append(f)


        me = bpy.data.meshes.new("Loft")
        me.from_pydata(verts,[], faces)
        me.update()
        newobj = bpy.data.objects.new("Loft", me)
        #newobj.data = me
        scn = context.scene
        scn.objects.link(newobj)
        scn.objects.active = newobj
        newobj.select = True
        bpy.ops.object.shade_smooth()

        #the object stores its own steps and spans
        #this way the driver will know how to deform the mesh
        newobj["steps"] = steps
        newobj["spans"] = spans

        if intype==3:
            newobj['tension'] = context.scene.tension
            newobj['bias'] = context.scene.bias


        if context.scene.dodriver:
            createloftdriver(objs, newobj, intype)

        return {'FINISHED'}

class UpdateFix(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "mesh.update_fix"
    bl_label = "Update fix"

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

    def execute(self, context):
        #print("------------")
#       for it in bpy.app.driver_namespace:
#           print(it)
        bpy.app.driver_namespace['loftdriver'] = loftdriver
        bpy.app.driver_namespace['birail1driver'] = birail1driver
        for obj in context.scene.objects:
            if obj.type=="CURVE" and obj.animation_data!=None and len(obj.animation_data.drivers)>0:
                for drv in obj.animation_data.drivers:
                    if drv.data_path=='["driver"]':
                        cad = drv.driver.expression
                        drv.driver.expression = ""
                        drv.driver.expression = cad

        return {'FINISHED'}


#derives a curve at a given parameter
def deriv(curve, t, unit=False):

    a = t + 0.001
    if t==1: a=t-0.001

    pos = calct(curve, t)
    der = (pos-calct(curve, a))/(t-a)
    if unit:
        der = der/der.magnitude
    return der

### BIRAIL1 BLOCK


#see explanation video about the construction
#http://vimeo.com/25455967

#calculates birail vertices

### TODO: when the 3 curves are coplanar it should fail, cause the cross product. check that
def birail1(objs, steps, spans, proportional):

    profile=objs[0]
    ### TODO: identify which path is left or right
    path1 = objs[1]
    path2 = objs[2]

    trans = []

    r0 = [calct(path1,0), calct(path2, 0)]
    r0mag = (r0[1]-r0[0]).magnitude

    for i in range(0, steps):
        u = i/(steps-1)
        appr0 = r0[0]+(r0[1]-r0[0])*u
        trans.append(calct(profile, u)-appr0)

    der10 = deriv(path1, 0)
    der20 = deriv(path2, 0)

    verts = []

    mult = 1.0

    for i in range(0, spans):
        v = i/(spans-1)
        r = [calct(path1, v),calct(path2, v)]
        rmag = (r[1]-r[0]).magnitude

        der1 = deriv(path1, v)
        der2 = deriv(path2, v)

        angle1 = der10.angle(der1)
        angle2 = der20.angle(der2)

        #if angle1!=0.0 and angle2!=0: we can avoid some operations by doing this check but im lazy
        cr1 = der1.cross(der10)
        rot1 = Matrix.Rotation(-angle1, 3, cr1)

        cr2 = der2.cross(der20)
        rot2 = Matrix.Rotation(-angle2, 3, cr2)

        if proportional:
            mult = rmag/r0mag

        for j in range(0, steps):
            u = j/(steps-1)

            app = r[0]+(r[1]-r[0])*u

            newtr1 = trans[j].copy()
            newtr1.rotate(rot1)

            newtr2 = trans[j].copy()
            newtr2.rotate(rot2)

            r1 = (newtr1-trans[j])*(1-u)
            r2 = (newtr2-trans[j])*(u)

            res = r1+r2+app+mult*trans[j]

            verts.append(res)

    return verts


#same as loft driver
### TODO check if it is registered
def birail1driver(name, objs):

    objs = objs.split(";")
    #objs = objs[0:-1]

    for i, l in enumerate(objs):
        objs[i] = bpy.data.objects[l]

    try:
        resobj = bpy.data.objects[name]
        spans = resobj["spans"]
        steps = resobj["steps"]
        prop = resobj["prop"]

    except:
        curve = bpy.context.object
        curve.driver_remove('["driver"]')
        return False

    vxs = birail1(objs, steps, spans, prop)

    me = resobj.data

    for i in range(0, len(me.vertices)):
        me.vertices[i].co = vxs[i]
    me.update()
    return spans

def createbirail1driver(objs, res):

    line = ""
    for obj in objs:
        line+=obj.name+";"
    line=line[0:-1]
    for obj in objs:
        obj["driver"] = 1.0
        obj.driver_add('["driver"]')
        obj.animation_data.drivers[0].driver.expression = "birail1driver('"+ res.name +"', '" + line + "')"

### TODO: check polls and if initial variables are ok to perform the birail
class Birail1Operator(bpy.types.Operator):

    bl_idname = "mesh.birail1_operator"
    bl_label = "Birail between 3 bezier curves"

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

    def execute(self, context):

        objs = bpy.selection

        if len(objs)!=3:
            self.report({'ERROR'},"Please select 3 curves")
            return {'FINISHED'}

        scn = context.scene
        spans = scn.spans
        steps = scn.steps
        prop = scn.proportional

        verts = birail1(objs, steps, spans, prop)

        if verts!=[]:
            faces=[]

            nfaces = (steps-1)*(spans-1)

            for i in range(0, nfaces):
                d = int(i/(steps-1))
                f = [i+d+1, i+d, i+d+steps, i+d+steps+1 ]
                faces.append(f)

            me = bpy.data.meshes.new("Birail")
            me.from_pydata(verts,[], faces)
            me.update()
            newobj = bpy.data.objects.new("Birail", me)
            newobj.data = me

            scn.objects.link(newobj)
            scn.objects.active = newobj
            newobj.select = True
            bpy.ops.object.shade_smooth()
            newobj['steps']=steps
            newobj['spans']=spans
            newobj['prop']=prop

            if scn.dodriver:
                createbirail1driver(objs, newobj)

        return {'FINISHED'}

#register the drivers
bpy.app.driver_namespace['loftdriver'] = loftdriver
bpy.app.driver_namespace['birail1driver'] = birail1driver

### MERGE SPLINES BLOCK

#reads spline points
#spl spline to read
#rev reads the spline forward or backwards
def readspline(spl, rev=0):
    res = []

    if spl.type=="BEZIER":
        points = spl.bezier_points
        for p in points:
            if rev:
                h2 = p.handle_left
                h1 = p.handle_right
                h2type = p.handle_left_type
                h1type = p.handle_right_type
            else:
                h1 = p.handle_left
                h2 = p.handle_right
                h1type = p.handle_left_type
                h2type = p.handle_right_type

            co = p.co
            res.append([h1, co, h2, h1type, h2type])
    if rev:
        res.reverse()

    return res

#returns a new merged spline
#cu curve object
#pts1 points from the first spline
#pts2 points from the second spline

def merge(cu, pts1, pts2):
    newspl = cu.data.splines.new(type="BEZIER")
    for i, p in enumerate(pts1):

        if i>0: newspl.bezier_points.add()
        newspl.bezier_points[i].handle_left = p[0]
        newspl.bezier_points[i].co = p[1]
        newspl.bezier_points[i].handle_right = p[2]
        newspl.bezier_points[i].handle_left_type = p[3]
        newspl.bezier_points[i].handle_right_type = p[4]

    newspl.bezier_points[-1].handle_right_type="FREE"
    newspl.bezier_points[-1].handle_left_type="FREE"

    newspl.bezier_points[-1].handle_right = pts2[0][2]


    for j in range(1, len(pts2)):

        newspl.bezier_points.add()
        newspl.bezier_points[-1].handle_left = pts2[j][0]
        newspl.bezier_points[-1].co = pts2[j][1]
        newspl.bezier_points[-1].handle_right = pts2[j][2]
        newspl.bezier_points[-1].handle_left_type = pts2[j][3]
        newspl.bezier_points[-1].handle_right_type = pts2[j][4]

    return newspl

#looks if the splines first and last points are close to another spline
### TODO: Check if the objects selected are valid
### if possible implement nurbs

class MergeSplinesOperator(bpy.types.Operator):

    bl_idname = "curve.merge_splines"
    bl_label = "Merges spline points inside a limit"

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

    def execute(self, context):
        curves = []
        limit = context.scene.limit
        print("merguing")
        for obj in context.selected_objects:
            if obj.type=="CURVE":
                curves.append(obj)

        for cu in curves:
            splines = []
            for spl in cu.data.splines:
                splines.append(spl)
            print(splines)
            #compares all the splines inside a curve object
            for spl1 in splines:
                for spl2 in splines:
                    print(spl1, spl2)
                    if spl1!=spl2 and spl1.type==spl2.type=="BEZIER" and spl1.use_cyclic_u==spl2.use_cyclic_u==False:
                        print("not cyclic")
                        if len(spl1.bezier_points)>1  and len(spl2.bezier_points)>1:

                            #edges of the 2 splines
                            p1i = spl1.bezier_points[0].co
                            p1f = spl1.bezier_points[-1].co
                            p2i = spl2.bezier_points[0].co
                            p2f = spl2.bezier_points[-1].co

                            if dist(p1i, p2i)<limit:
                                print("join p1i p2i")

                                p1 = readspline(spl2, 1)
                                p2 = readspline(spl1)
                                res=merge(cu, p1, p2)
                                cu.data.splines.remove(spl1)
                                cu.data.splines.remove(spl2)
                                splines.append(res)
                                break
                            elif dist(p1i, p2f)<limit:
                                print("join p1i p2f")
                                p1 = readspline(spl2)
                                p2 = readspline(spl1)
                                res = merge(cu, p1, p2)
                                cu.data.splines.remove(spl1)
                                cu.data.splines.remove(spl2)
                                splines.append(res)
                                break
                            elif dist(p1f, p2i)<limit:
                                print("join p1f p2i")
                                p1 = readspline(spl1)
                                p2 = readspline(spl2)
                                res = merge(cu, p1, p2)
                                cu.data.splines.remove(spl1)
                                cu.data.splines.remove(spl2)
                                splines.append(res)
                                break
                            elif dist(p1f, p2f)<limit:
                                print("unir p1f p2f")
                                p1 = readspline(spl1)
                                p2 = readspline(spl2, 1)
                                res = merge(cu, p1, p2)
                                cu.data.splines.remove(spl1)
                                cu.data.splines.remove(spl2)
                                splines.append(res)
                                break

        #splines.remove(spl1)
        return {'FINISHED'}

### NURBS WEIGHTS

class NurbsWeightsPanel(bpy.types.Panel):
    bl_label = "Nurbs Weights"
    bl_space_type = "VIEW_3D"
    bl_region_type = "TOOLS"
    #bl_context = "data"

    @classmethod
    def poll(cls, context):
        if context.active_object != None and context.active_object.type =="CURVE" and context.active_object.data.splines.active!=None and context.active_object.data.splines.active.type=="NURBS":
            return True
        else:
            return False


    def draw(self, context):
        layout = self.layout

        obj = context.object

        for p in obj.data.splines.active.points:
            if p.select:
                row = layout.row()
                row.prop(p,  "weight")

### CUT / SUBDIVIDE CURVE
#obj curve object
#t parameter to perform the cut or split
#method True = subdivide, Flase= Split

def cutcurve(obj, t, method=True):

    #flocal to global transforms or viceversa


    #retrieves the active spline or the first spline if there no one active

    spline=None
    if obj.data.splines.active==None:
        for sp in obj.data.splines:
            if sp.type=="BEZIER":
                spline= sp
                break
    else:
        if obj.data.splines.active.type!="BEZIER":
            return False
        else:
            spline=obj.data.splines.active

    if spline==None: return False

    points = spline.bezier_points
    nsegs = len(points)-1

    #transform global t into local t1
    d = 1.0/nsegs
    seg = int(t/d)
    t1 = t/d-int(t/d)
    if t>=1.0:
        t=1.0
        seg-=1
        t1 = 1.0

    #if t1 is not inside a segment dont perform any action
    if t1>0.0 and t1<1.0:
        mw = obj.matrix_world
        mwi = obj.matrix_world.copy().inverted()

        pts = getbezpoints(spline, mw, seg)

        #position on the curve to perform the action
        pos = calct(obj, t)

        #De Casteljau's algorithm to get the handles
        #http://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm
        h1 = pts[0]+(pts[1]-pts[0])*t1
        h4 = pts[2]+(pts[3]-pts[2])*t1
        r = pts[1]+(pts[2]-pts[1])*t1
        h2 = h1+(r-h1)*t1
        h3 = r+(h4-r)*t1


        if method:
            #SUBDIVIDE
            splp = []
            type = "ALIGNED"
            for i, p in enumerate(points):
                ph1 = p.handle_left*mw
                pco = p.co*mw
                ph2 = p.handle_right*mw
                ph1type = p.handle_left_type
                ph2type = p.handle_right_type
                splp.append([ph1, pco, ph2, ph1type, ph2type])
                p.handle_left_type = type
                p.handle_right_type = type

                if i==seg:
                    splp[-1][2]=h1
                    splp.append([h2, pos, h3, type, type])

                if i==seg+1:
                    splp[-1][0]=h4
                    splp[-1][3]=type
                    splp[-1][4]=type
                #if i dont set all the handles to "FREE"
                #it returns weirds result
                ### TODO: find out how to preserve handle's types

            points.add()
            for i, p in enumerate(points):
                p.handle_left_type = "FREE"
                p.handle_right_type ="FREE"
                p.handle_left = splp[i][0]*mwi
                p.co = splp[i][1]*mwi
                p.handle_right=splp[i][2]*mwi
                p.handle_left_type = splp[i][3]
                p.handle_right_type =splp[i][4]
        else:
            #SPLIT CURVE
            spl1 = []
            spl2 = []
            k=0 #changes to 1 when the first spline is processed
            type = "ALIGNED"
            for i, p in enumerate(points):
                ph1 = p.handle_left*mw
                pco = p.co*mw
                ph2 = p.handle_right*mw
                ph1type = p.handle_left_type
                ph2type = p.handle_right_type
                if k==0:
                    spl1.append([ph1, pco, ph2, ph1type, ph2type])
                else:
                    spl2.append([ph1, pco, ph2, ph1type, ph2type])

                if i==seg:
                    spl1[-1][2]=h1
                    spl1.append([h2, pos, h3, type, type])
                    spl2.append([h2, pos, h3, type, type])
                    k=1

                if i==seg+1:
                    spl2[-1][0]=h4
                    spl2[-1][3]=type
                    spl2[-1][4]=type

            sp1 = obj.data.splines.new(type="BEZIER")
            for i, p in enumerate(spl1):
                if i>0: sp1.bezier_points.add()
                sp1.bezier_points[i].handle_left_type = "FREE"
                sp1.bezier_points[i].handle_right_type ="FREE"
                sp1.bezier_points[i].handle_left = spl1[i][0]*mwi
                sp1.bezier_points[i].co = spl1[i][1]*mwi
                sp1.bezier_points[i].handle_right=spl1[i][2]*mwi
                #i tried to preserve the handles here but
                #didnt work well

                sp1.bezier_points[i].handle_left_type = spl1[i][3]
                sp1.bezier_points[i].handle_right_type =spl1[i][4]

            sp2 = obj.data.splines.new(type="BEZIER")
            for i, p in enumerate(spl2):
                if i>0: sp2.bezier_points.add()
                sp2.bezier_points[i].handle_left_type = "FREE"
                sp2.bezier_points[i].handle_right_type = "FREE"
                sp2.bezier_points[i].handle_left = spl2[i][0]*mwi
                sp2.bezier_points[i].co = spl2[i][1]*mwi
                sp2.bezier_points[i].handle_right=spl2[i][2]*mwi
                sp2.bezier_points[i].handle_left_type = spl2[i][3]
                sp2.bezier_points[i].handle_right_type =spl2[i][4]

            obj.data.splines.remove(spline)

class CutCurveOperator(bpy.types.Operator):
    """Subdivide / Split a bezier curve"""
    bl_idname = "curve.cut_operator"
    bl_label = "Cut curve operator"

    #cut or split
    method = bpy.props.BoolProperty(default=False)
    t = 0.0

    @classmethod
    def poll(self, context):
        if context.active_object!=None:
            return context.active_object.type=="CURVE"
        else:
            return False


    def modal(self, context, event):

        if event.type == 'MOUSEMOVE':
            #full screen width
            #not tested for multiple monitors
            fullw = context.window_manager.windows[0].screen.areas[0].regions[0].width

            self.t = event.mouse_x/fullw

            #limit t to [0,...,1]
            if self.t<0:
                self.t=0.0
            elif self.t>1.0:
                self.t=1.0

            obj = context.object
            pos = calct(obj, self.t)

            #if calct() detects a non bezier spline returns false
            if pos==False:
                return {'CANCELLED'}
            cursor(pos)

        elif event.type == 'LEFTMOUSE':
            #print(self.method, self.t)
            cutcurve(context.object, self.t, self.method)
            return {'FINISHED'}

        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            #print("Cancelled")

            return {'CANCELLED'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):

        if context.object:
            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "No active object, could not finish")
            return {'CANCELLED'}

### CURVE SNAP BLOCK

class AllowCurveSnap(bpy.types.Operator):
    bl_idname = "curve.allow_curve_snap"
    bl_label = "Allow Curve Snap"

    add = bpy.props.BoolProperty()

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

    def execute(self, context):
        add = self.add

        scn = context.scene

        if add==False:
            for helper in context.scene.objects:
                for key  in helper.keys():
                    print(key)
                    if key=="is_snap_helper" and helper[key]==1:
                        scn.objects.unlink(helper)
        else:
            #objs = context.selected_objects
            objs = context.scene.objects
            for obj in objs:
                if obj.type=="CURVE":

                    res = obj.data.resolution_u

                    obj.data.resolution_u = 100

                    me = obj.to_mesh(scene=scn, apply_modifiers=True,settings = "PREVIEW" )
                    obj.data.resolution_u = res
                    newobj = bpy.data.objects.new(obj.name+"_snap", me)
                    scn.objects.link(newobj)
                    newobj.layers = obj.layers
                    newobj.matrix_world = obj.matrix_world
                    newobj["is_snap_helper"]=True
                    newobj.hide_render=True
                    newobj.hide_select = True
                    cons = newobj.constraints.new(type="COPY_TRANSFORMS")
                    cons.target =obj

        return {'FINISHED'}

def menu_func(self, context):
    self.layout.operator("curve.allow_curve_snap").add=True
    self.layout.operator("curve.allow_curve_snap", text = "Delete Snap Helpers").add=False

### PANEL
class CurvePanel(bpy.types.Panel):
    bl_label = "Curve Tools"
    bl_space_type = "VIEW_3D"
    bl_region_type = "TOOLS"
    #bl_options = {'REGISTER', 'UNDO'}
    #bl_context = "data"
    bl_options = {'DEFAULT_CLOSED'}
    steps = IntProperty(min=2, default = 12)

    @classmethod
    def poll(cls, context):
        return (context.active_object != None) and (context.active_object.type=="CURVE")
    def draw(self, context):
        layout = self.layout

        obj = context.object
        scn = context.scene

        align = True
        row = layout.row(align=align)

        row.prop(context.scene, "intype", text = "")
        row.prop(context.scene, "dodriver", text = "Driven")
        if scn.intype=='3': #Hermite interp
            row = layout.row(align=align)
            row.prop(scn, "tension")
            row.prop(scn, "bias")

        row = layout.row(align=align)
        row.prop(context.scene, "steps")
        row.prop(context.scene, "spans")
        row = layout.row(align=align)
        row.operator("mesh.loft_operator", text = "Loft")
        row.operator("mesh.update_fix", text = "Update Fix")
        row = layout.row(align=align)
        row.operator("mesh.birail1_operator", text = "Birail 1")
        row.prop(context.scene, "proportional", text = "Proportional")
        row = layout.row(align=align)
        row.operator("curve.arc_length_operator", text = "Calc Length")
        row.prop(context.scene, "clen", text = "")
        row = layout.row(align=align)
        row.operator("curve.merge_splines", text = "Merge")
        row.prop(context.scene,"limit",  text = "Limit")
        row = layout.row(align=align)
        row.operator("curve.cut_operator", text="Subdivide").method=True
        row.operator("curve.cut_operator", text="Split").method=False

#       col1 = row.column()
#       col1.prop(context.scene, "intype", text = "")
#       col1.prop(context.scene, "dodriver", text = "Driven")
#       row = layout.row(align=align)
#       col2 = row.column(align=align)
#       col2.prop(context.scene, "steps")
#       col2.prop(context.scene, "spans")
#       row = layout.row(align=align)
#       row.operator("mesh.loft_operator", text = "Loft")
#       row.operator("mesh.update_fix", text = "Update Fix")
#       row = layout.row(align=align)
#       row.operator("mesh.birail1_operator", text = "Birail 1")
#       row.prop(context.scene, "proportional", text = "Proportional")
#       row = layout.row(align=align)
#       row.operator("curve.arc_length_operator", text = "Calc Length")
#       row.prop(context.scene, "clen", text = "")
#       row = layout.row(align=align)
#       row.operator("curve.merge_splines", text = "Merge")
#       row.prop(context.scene,"limit",  text = "Limit")
#       row = layout.row(align=align)
#       row.operator("curve.cut_operator", text="Subdivide").method=True
#       row.operator("curve.cut_operator", text="Split").method=False

classes = [AllowCurveSnap, Selection, LoftOperator, Birail1Operator,
            ArcLengthOperator, UpdateFix, MergeSplinesOperator, CutCurveOperator, NurbsWeightsPanel, CurvePanel]

bpy.app.driver_namespace['loftdriver'] = loftdriver
bpy.app.driver_namespace['birail1driver'] = birail1driver

def register():

    domenu=1
    for op in dir(bpy.types):
        if op=="CURVE_OT_allow_curve_snap":
            domenu=0
            break
    if domenu:
        bpy.types.VIEW3D_MT_object_specials.append(menu_func)

    for c in classes:
        bpy.utils.register_class(c)

def unregister():
    for c in classes:
        bpy.utils.unregister_class(c)

    bpy.types.VIEW3D_MT_object_specials.remove(menu_func)


if __name__ == "__main__":
    register()
