#
# fabaroni.py
#
# GUI for sending file to FabClass 3D printer.
#
# Kate, Siggi 
# MIT Media Lab 11/18/2007
#
# (c) Massachusetts Institute of Technology 2007
# Permission granted for experimental and personal use;
# license for commercial sale available from MIT
#

import serial
import sys
import time
from Tkinter import *
from tkFileDialog import *

WINDOW = 600
eps = 0.1
saveflag = 0
paused = True
commands = []
info = []
command_num = 0 # which command to send next

###############################################################################
# METHODS FOR PARSING THE FAB INPUT FILE
###############################################################################

# expects string input of the form "x,y,z" returns 3-value tuple (x, y, z) where x, y, and z are integers
def parseCommand(command):
    args = command.rstrip().split(',')
    x = int(args[0])
    y = int(args[1])
    z = int(args[2])  
    return x, y, z

# make sure no tuple will produce values identical to the header packet
def escapeCommand(t):
    x, y, z = t
    if (x == 123):
        x = 124
    if (y  == 123):
        y = 124
    if (z == 123):
        z = 124
    return x, y, z
    
# convert tuples of the form x, y, z to 8 byte integers that can be sent over serial
def encodeRotation(t):
    i = 0x00 # need to test if this is truly only 8 bits.
    x, y, z = t
    if (x < 0):
        i |= 0x01
    if (y < 0):
        i |= 0x02
    if (z < 0):
        i |= 0x04      
    return i
    
# make tuples absolute
def makeAbs(t):
    x, y, z = t
    if (x < 0):
        x = -x
    if (y < 0):
        y = -y
    if (z < 0):
        z = -z
    return x,y,z

# return true if s is not empty
def nempty(s):
    return s != ''

###############################################################################
# MOTOR STUFF
###############################################################################

def send2motor():
    global commands, info
    #file = open(filename, 'r');
    # read everything from input file
    # this is a little slow -- if the performance is awful, we can tweak it later.
    #s = ""
    #for line in file:
    #    s += line.rstrip()
    #print s
    #file.close()
    s = text_editor.get(1.0, END)
    s = s.rstrip()
    #print s
    n = len(s);

    # split strings into commands (they're separated by semi-colons)
    commands = s.split(';') 
    print commands
    # filter out any blank lines
    commands = filter(nempty, commands)
    
    # break commands into tuples
    commands = map(parseCommand, commands)
    # make sure none of the commands will be confused for the header packet
    commands = map(escapeCommand, commands)
    # encode the rotations
    info = map(encodeRotation, commands)
    # make absolute
    commands = map(makeAbs, commands)
    print commands
    print info

###############################################################################
# SERIAL SETUP
###############################################################################
# transmit over serial
#ser = serial.Serial('COM1', 115200, timeout=10) # windows
#ser = serial.Serial('/dev/ttyS0', 115200, timeout=10000) # Linux
ser = serial.Serial('/dev/cu.USA19H1d1P1.1', 115200, timeout=10) # for keyspan usb adapter (Mac OS X)
# 8 data bits is default
ser.open()

###############################################################################
# TK / GUI STUFF
###############################################################################

def idle(parent,elem):
    global saveflag, paused, ser, commands, info, command_num

    #if (paused):
    #    print "NOT sending to printer!"
    if (not paused and command_num < len(commands)):
        # we're printing -- send another instruction.
        x, y, z = commands[command_num]
        send_command(x, y, z, info[command_num])
        command_num += 1 # send the next command on next idle loop.
    elif (not paused):
        string_msg.set("done printing")
    parent.update_idletasks()
    parent.update()
    parent.after_idle(idle,parent,elem)

root = Tk()
root.title('send to printer (q to exit)')
root.bind('q','exit')

# send a rotation to the printer
def send_command(x, y, z, i):
    string_msg.set("moving: " + str(x) + ", " + str(y) + ", " + str(z) + " encoding:" + str(i))
    if (not paused):
        return
    ok2send = ser.read()
    
    # send header
    ser.write(chr(123))
    # pause
    time.sleep(0.1)
    # send encoding byte
    ser.write(chr(i))
    # send steps
    ser.write(chr(x))
    ser.write(chr(y))
    ser.write(chr(z))
    # pause
    time.sleep(0.1)
    info2 = ser.read()
    x2 = ser.read()
    y2 = ser.read()
    z2 = ser.read()

    if (ord(info2) != i) or (ord(x2) != x) or (ord(y2) != y) or (ord(z2) != z):
        print 'ERROR!'
        print ord(info2)
        print ord(x2)
        print ord(y2)
        print ord(z2)
        print i
        print x
        print y
        print z
    # pause
    time.sleep(0.1)

def load_file():
    input_file_name = string_input_file.get()
    print input_file_name
    input_file = open(input_file_name,'rb')
    filetext = input_file.read()
    text_editor.delete("1.0",END)
    text_editor.insert("1.0",filetext)

# open file to use as input
def input_open():
    print "called"
    filename = askopenfilename()
    print "input"
    string_input_file.set(filename)
    if (filename.find('.fab') != -1):
        print "loading: ", filename
        load_file()
        string_msg.set("loaded file: " + filename)
    else:
        string_msg.set("unsupported input file format")
        root.update()

def start_printing():
    global paused
    # load and parse data
    send2motor()
    paused = False
    command_num = 0 # start from the beginning
    print "START printing"

def pause_printing():
    global paused
    paused = True
    print "PAUSE printing"

def resume_printing():
    global paused
    paused = False
    print "RESUME printing"

def getNumRotations():
    v = string_input_rotations.get()
    try:
        v = int(v)
    except:
        v = 0
    return v

def move_x_plus():
    send_command(getNumRotations(), 0, 0, 0);
    string_msg.set("done moving")

def move_x_minus():
    send_command(getNumRotations(), 0, 0, 1);
    string_msg.set("done moving")
    
def move_y_plus():
    send_command(0, getNumRotations(), 0, 0);
    string_msg.set("done moving")

def move_y_minus():
    send_command(0, getNumRotations(), 0, 2);
    string_msg.set("done moving")
    
def move_z_plus():
    send_command(0, 0, getNumRotations(), 0);
    string_msg.set("done moving")

def move_z_minus():
    send_command(0, 0, getNumRotations(), 4);
    string_msg.set("done moving")

# canvas for FABARONI logo!
canvas = Canvas(root, width=WINDOW, height=108, background='white')
photo = PhotoImage(file="fabaroni.gif")
canvas.create_image(300, 0, anchor=N, image=photo)
canvas.pack()

# input frame
input_frame = Frame(root)
widget_input_file = Button(input_frame, text="input:",command=input_open)
widget_input_file.pack(side='left')
string_input_file = StringVar()
string_input_file.set('test.fab')
widget_filename = Entry(input_frame, width=17, bg='white', textvariable=string_input_file)
widget_filename.pack(side='left')
input_frame.pack()

# frame for editor
editor_frame = Frame(root)
text_editor = Text(editor_frame, bg = "white", bd=5)
editor_frame.pack()
text_editor.pack()

# controls for sending to the printer
control_frame = Frame(root)
start_button = Button(control_frame, text="parse and send to machine", command=start_printing)
start_button.pack(side='left')
pause_button = Button(control_frame, text="pause", command=pause_printing)
pause_button.pack(side='left')
resume_button = Button(control_frame, text="resume", command=resume_printing)
resume_button.pack(side='left')
control_frame.pack()

# manual movement buttons
move_frame = Frame(root)
xplus_button = Button(move_frame, text="x+", command=move_x_plus)
xplus_button.pack(side='left')
xminus_button = Button(move_frame, text="x-", command=move_x_minus)
xminus_button.pack(side='left')
yplus_button = Button(move_frame, text="y+", command=move_y_plus)
yplus_button.pack(side='left')
yminus_button = Button(move_frame, text="y-", command=move_y_minus)
yminus_button.pack(side='left')
zplus_button = Button(move_frame, text="z+", command=move_z_plus)
zplus_button.pack(side='left')
zminus_button = Button(move_frame, text="z-", command=move_z_minus)
zminus_button.pack(side='left')
string_input_rotations = StringVar()
string_input_rotations.set('10')
widget_rotations = Entry(move_frame, width=3, bg='white', textvariable=string_input_rotations)
widget_rotations.pack(side='left')
move_frame.pack()

# message box
msg_frame = Frame(root)
string_msg = StringVar()
widget_msg = Label(msg_frame, textvariable = string_msg)
widget_msg.pack()
msg_frame.pack()

# check for user input from command line
if (len(sys.argv) != 1):
    fname = sys.argv[1]
    string_input_file.set(fname)
    load_file()

#
# start idle loop
#
root.after(100,idle,root, editor_frame)
root.mainloop()

