# https://code.activestate.com/recipes/576871-spline-demonstrator/

import tkinter
from tkinter import*

def mod(a,b):
    return ( a % b)

def StandardKnot():
    global m
    knot = []
    L = len(points) - 2
    if L <= m -2 :
        print('make more points than m')
        return []
    for i in range(m - 1):
        knot.append(0)
    for i in range(L - m + 3):
        knot.append(i)
    for i in range(m):
        knot.append(L - m + 3)
    return knot

def Nopen(k,m,t,knot):
    if m <= 1:
        if t < knot[k] or t >= knot[k + 1]:
            Sum = 0.0
        else:
            Sum = 1.0
    else:
        d = knot[k+m-1]-knot[k]
        if d != 0:
            Sum = (t-knot[k])*Nopen(k,m-1,t,knot)/d
        else:
            Sum = 0.0
        d = knot[k+m] - knot[k+1]
        if d != 0:
            Sum = Sum + (knot[k+m] - t)*Nopen(k + 1,m-1,t,knot)/d
    return Sum

def Nclosed(k,m,t,knot):
    L = len(points)
    z = mod(t-k,L)
    if z <= 0:
        z += L
    return Nopen(0,m,z,knot)

def P(t,knot,Ncycle):
    L = len(points)
    SumX = 0.0
    SumY = 0.0
    for k in range(L):
        n = Ncycle(k,m,t,knot)
        SumX = SumX + n * points[k][0]
        SumY = SumY + n * points[k][1]
    return [SumX,SumY]

def plot():
    global m
    global points
    if lOpen:
       knot = StandardKnot()
       if len(knot) == 0:
           return
       print(knot)
       print(points)
       x = points[0][0]
       y = points[0][1]
       t = 0.0
       step = 0.01
       L = len(points)
       while t <= L - m  + 1:
           p = P(t,knot,Nopen)
           C.create_line(x, y, p[0], p[1])
           x = p[0]
           y = p[1]
           t = t + step
    else:
       L = len(points)
       knot = range(L)
       print(knot)
       print(points)
       p = P(0.0,knot,Nclosed)
       x = p[0]
       y = p[1]
       step = 0.01
       t = step
       L = len(points)
       while t <= L:
           p = P(t,knot,Nclosed)
           C.create_line(x, y, p[0], p[1])
           x = p[0]
           y = p[1]
           t = t + step

def Spaceout():
    global points
    newpoints = []
    L = len(points)
    if lOpen:
       newpoints.append(points[0])
       knot = StandardKnot()
       if len(knot) == 0:
           return
       for t in range( 1, L - m  + 2):
           p = P(t - 0.5,knot,Nopen)
           x = points[t][0] - p[0]
           y = points[t][1] - p[1]
           newpoints.append([points[t][0] + x,points[t][1] + y])
       newpoints.append(points[-1])
    else:
       knot = range(L)
       for t in range(L):
           p = P(mod(t + 1.5,L),knot,Nclosed)
           x = points[t][0] - p[0]
           y = points[t][1] - p[1]
           newpoints.append([points[t][0] + x,points[t][1] + y])
    points = newpoints
    plot()

def do_mouse(eventname):
    def mouse_binding(event):
        global points
        if eventname == "Button-1":
            x = event.x
            y = event.y
            print(x, y)
            if x > 50 and y > 50:
                points.append([x,y])
                C.create_oval(x - 6,y - 6, x + 6, y + 6)
                C.create_text(x, y, text = '%d' %(len(points)))
    fram.bind_all( '<%s>' %eventname, mouse_binding)

def wipe():
    global points
    C.delete( ALL)
    points = []

def fetch():
    global m
    m = mEntry.get()
    m = int(m)
    print('m is now %d' % m)


def CycleClosed(i):
    global lOpen
    lOpen = i
    if lOpen:
        print('Cycle is open')
    else:
        print('Cycle is closed')


def Blending():
    global points
    L = len(points)
    if L <= m:
        print('Make some more points first!')
        return
    if lOpen:
       knot = StandardKnot()
       if len(knot) == 0:
           return
    else:
       knot = range(L)
    print(knot)
    x0 = ScreenW * 0.2
    y0 = ScreenH * 0.2
    scaley = ScreenH * 0.7
    scalex = ScreenW / L * 0.7
    for k in knot:
        x1 = k * scalex + x0
        y1 = ScreenH - y0
        C.create_line(x1, y1, x1, y1 + 20)
    for k in range(L):
        t = 0.0
        step = 0.1
        x1 = x0
        y1 = y0
        if lOpen:
            z = L - m + 1
        else:
            z = L
        while t <= z:
            if lOpen:
               p = Nopen(k,m,t,knot)
            else:
               p = Nopen(0,m,mod(t-k,L),knot)
            x2 = t * scalex + x0
            y2 = ScreenH - p * scaley - y0
            C.create_line(x1, y1, x2, y2)
            x1 = x2
            y1 = y2
            t = t + step

m = 3
lOpen = True
ScreenH = 600
ScreenW = 800
root = Tk()
root.title('Left click to add some points, then click [Plot] to draw the b-spline.')
fram = Frame(root)
rad1 = Radiobutton(fram, text = 'Open',value=1,command=(lambda : CycleClosed(True)))
rad1.pack(side = LEFT)
rad2 = Radiobutton(fram, text = 'Closed',value=0,command=(lambda : CycleClosed(False)))
rad2.pack(side = LEFT)
rad1.select()
Label(fram, text = 'M:').pack(side = LEFT,padx = 20)
mEntry = Entry(fram)
mEntry.insert(0,m)
mEntry.focus()
mEntry.bind('<Return>', (lambda event: fetch()))
mEntry.pack(side = LEFT)
butt1 = Button(fram, text = '  Plot  ',command = plot)
butt1.pack(side = LEFT)
butt3 = Button(fram, text = '  Wipe  ',command = wipe)
butt3.pack(side = LEFT)
butt4 = Button(fram, text = 'Blending',command = Blending)
butt4.pack(side = LEFT)
butt5 = Button(fram, text = 'Spaceout',command = Spaceout)
butt5.pack(side = LEFT)
fram.pack(side = TOP)
C = Canvas(root, width = ScreenW, height = ScreenH)
C.pack()
do_mouse('Button-1')
do_mouse('Button-3')
points = []
root.mainloop()

# https://code.activestate.com/recipes/576871-spline-demonstrator/

import tkinter
from tkinter import*

def mod(a,b):
    return ( a % b)

def StandardKnot():
    global m
    knot = []
    L = len(points) - 2
    if L <= m -2 :
        print('make more points than m')
        return []
    for i in range(m - 1):
        knot.append(0)
    for i in range(L - m + 3):
        knot.append(i)
    for i in range(m):
        knot.append(L - m + 3)
    return knot

def Nopen(k,m,t,knot):
    if m <= 1:
        if t < knot[k] or t >= knot[k + 1]:
            Sum = 0.0
        else:
            Sum = 1.0
    else:
        d = knot[k+m-1]-knot[k]
        if d != 0:
            Sum = (t-knot[k])*Nopen(k,m-1,t,knot)/d
        else:
            Sum = 0.0
        d = knot[k+m] - knot[k+1]
        if d != 0:
            Sum = Sum + (knot[k+m] - t)*Nopen(k + 1,m-1,t,knot)/d
    return Sum

def Nclosed(k,m,t,knot):
    L = len(points)
    z = mod(t-k,L)
    if z <= 0:
        z += L
    return Nopen(0,m,z,knot)

def P(t,knot,Ncycle):
    L = len(points)
    SumX = 0.0
    SumY = 0.0
    for k in range(L):
        n = Ncycle(k,m,t,knot)
        SumX = SumX + n * points[k][0]
        SumY = SumY + n * points[k][1]
    return [SumX,SumY]

def plot():
    global m
    global points
    if lOpen:
       knot = StandardKnot()
       if len(knot) == 0:
           return
       print(knot)
       print(points)
       x = points[0][0]
       y = points[0][1]
       t = 0.0
       step = 0.01
       L = len(points)
       while t <= L - m  + 1:
           p = P(t,knot,Nopen)
           C.create_line(x, y, p[0], p[1])
           x = p[0]
           y = p[1]
           t = t + step
    else:
       L = len(points)
       knot = range(L)
       print(knot)
       print(points)
       p = P(0.0,knot,Nclosed)
       x = p[0]
       y = p[1]
       step = 0.01
       t = step
       L = len(points)
       while t <= L:
           p = P(t,knot,Nclosed)
           C.create_line(x, y, p[0], p[1])
           x = p[0]
           y = p[1]
           t = t + step

def Spaceout():
    global points
    newpoints = []
    L = len(points)
    if lOpen:
       newpoints.append(points[0])
       knot = StandardKnot()
       if len(knot) == 0:
           return
       for t in range( 1, L - m  + 2):
           p = P(t - 0.5,knot,Nopen)
           x = points[t][0] - p[0]
           y = points[t][1] - p[1]
           newpoints.append([points[t][0] + x,points[t][1] + y])
       newpoints.append(points[-1])
    else:
       knot = range(L)
       for t in range(L):
           p = P(mod(t + 1.5,L),knot,Nclosed)
           x = points[t][0] - p[0]
           y = points[t][1] - p[1]
           newpoints.append([points[t][0] + x,points[t][1] + y])
    points = newpoints
    plot()

def do_mouse(eventname):
    def mouse_binding(event):
        global points
        if eventname == "Button-1":
            x = event.x
            y = event.y
            print(x, y)
            if x > 50 and y > 50:
                points.append([x,y])
                C.create_oval(x - 6,y - 6, x + 6, y + 6)
                C.create_text(x, y, text = '%d' %(len(points)))
    fram.bind_all( '<%s>' %eventname, mouse_binding)

def wipe():
    global points
    C.delete( ALL)
    points = []

def fetch():
    global m
    m = mEntry.get()
    m = int(m)
    print('m is now %d' % m)


def CycleClosed(i):
    global lOpen
    lOpen = i
    if lOpen:
        print('Cycle is open')
    else:
        print('Cycle is closed')


def Blending():
    global points
    L = len(points)
    if L <= m:
        print('Make some more points first!')
        return
    if lOpen:
       knot = StandardKnot()
       if len(knot) == 0:
           return
    else:
       knot = range(L)
    print(knot)
    x0 = ScreenW * 0.2
    y0 = ScreenH * 0.2
    scaley = ScreenH * 0.7
    scalex = ScreenW / L * 0.7
    for k in knot:
        x1 = k * scalex + x0
        y1 = ScreenH - y0
        C.create_line(x1, y1, x1, y1 + 20)
    for k in range(L):
        t = 0.0
        step = 0.1
        x1 = x0
        y1 = y0
        if lOpen:
            z = L - m + 1
        else:
            z = L
        while t <= z:
            if lOpen:
               p = Nopen(k,m,t,knot)
            else:
               p = Nopen(0,m,mod(t-k,L),knot)
            x2 = t * scalex + x0
            y2 = ScreenH - p * scaley - y0
            C.create_line(x1, y1, x2, y2)
            x1 = x2
            y1 = y2
            t = t + step

m = 3
lOpen = True
ScreenH = 600
ScreenW = 800
root = Tk()
root.title('Left click to add some points, then click [Plot] to draw the b-spline.')
fram = Frame(root)
rad1 = Radiobutton(fram, text = 'Open',value=1,command=(lambda : CycleClosed(True)))
rad1.pack(side = LEFT)
rad2 = Radiobutton(fram, text = 'Closed',value=0,command=(lambda : CycleClosed(False)))
rad2.pack(side = LEFT)
rad1.select()
Label(fram, text = 'M:').pack(side = LEFT,padx = 20)
mEntry = Entry(fram)
mEntry.insert(0,m)
mEntry.focus()
mEntry.bind('<Return>', (lambda event: fetch()))
mEntry.pack(side = LEFT)
butt1 = Button(fram, text = '  Plot  ',command = plot)
butt1.pack(side = LEFT)
butt3 = Button(fram, text = '  Wipe  ',command = wipe)
butt3.pack(side = LEFT)
butt4 = Button(fram, text = 'Blending',command = Blending)
butt4.pack(side = LEFT)
butt5 = Button(fram, text = 'Spaceout',command = Spaceout)
butt5.pack(side = LEFT)
fram.pack(side = TOP)
C = Canvas(root, width = ScreenW, height = ScreenH)
C.pack()
do_mouse('Button-1')
do_mouse('Button-3')
points = []
root.mainloop()

# https://code.activestate.com/recipes/576871-spline-demonstrator/