Skip to content

Instantly share code, notes, and snippets.

@Frijol
Created August 22, 2014 17:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Frijol/71e1ebb71ef0ac3b58bb to your computer and use it in GitHub Desktop.
Save Frijol/71e1ebb71ef0ac3b58bb to your computer and use it in GitHub Desktop.
AutoFrost Cake Python GUI
# Last modified 12/14/2010
#
# Purpose: This code generates all parts of the cake creation GUI,
# converts user input to motor commands, and sends those commands to the arduino
from Tkinter import *
import sys
import serial
class Callable:
"""From the Python Cookbook 9.1, page 302"""
def __init__(self, func, *args, **kwds):
self.func = func
self.args = args
self.kwds = kwds
def __call__(self):
return apply(self.func, self.args, self.kwds)
def __str__(self):
return self.func.__name__
def distance((x1, y1),(x2, y2)):
"""Calculates the distance between two points"""
dist = ((x2-x1)**2 + (y2-y1)**2)**.5
return dist
# Below: filler definitions to enable drawing different nozzle shapes on the
# GUI. We never implemented this functionality, but the basic code structure
# is mostly here.
def create_smallstar():
pass
def create_bigstar():
pass
def create_diamond():
pass
def create_circle(x, y, color, canvas):
x1, y1 = (x - 4), (y - 4)
x2, y2 = (x + 4), (y + 4)
canvas.create_oval(x1, y1, x2, y2, fill = color, outline = color)
def create_moon():
pass
def create_toothedmoon():
pass
### PaintGUI sets up a window based on the cake dimensions. A circular cake
### is shown by a circle in a black window, while a regular cake is just
### a rectangular canvas. Mycake is the object-oriented implemenatation.
class Mycake:
def __init__(self, size, color):
self.paintcolor = 'white'
self.nozzletype = 'circle'
self.cakecolor = color
self.pointslist = []
self.currentlist = []
self.size = size
def PaintGUI(self):
root = Tk()
root.title('Team Cake')
root.resizable(0,0)
# Sets up circular or rectangular canvas, scaled from the size the user
# has entered
if len(self.size) == 1:
diam = self.size[0]
side = diam*50
canvas = Canvas(root, width=side, height=side, bg = 'black')
cake = canvas.create_oval(0,0,side,side,fill = self.cakecolor)
center = (side / 100.0, side / 100.0)
else:
canvas = Canvas(root, width = self.size[0]*50,
height = self.size[1]*50,
bg = self.cakecolor)
center =(self.size[0] / 100.0, self.size[1] / 100.0)
canvas.configure(cursor = 'crosshair')
# This is the paint-on-canvas function. When bound to a mouseclick
# event, it enables painting on the canvas.
def paint(event):
x = event.x
y = event.y
# Realsizepoint is the point on the canvas, scaled back into real
# inches. Listitem is the same information, plus the current color
# and nozzle type, for sending to the arduino.
realsizepoint = (event.x / 50.0, event.y / 50.0)
listitem = (event.x / 50.0, event.y / 50.0,
self.paintcolor, self.nozzletype)
def paintstuff():
if len(self.currentlist) == 0:
# Here, is we created the other polygon-drawing functions
# and chose which one to call based on the current nozzle
# type, differently shaped nozzles could show up on the
# paint GUI
create_circle(x, y, self.paintcolor, canvas)
self.currentlist.append(listitem)
# Space out points appropriately -- to prevent the user's
# mouse speed from controlling frosting density, only
# place points at regular intervals
else:
oldx = self.currentlist[-1][0]
oldy = self.currentlist[-1][1]
if distance((oldx, oldy), realsizepoint) > .125:
# Again, we could generalize for other nozzle shapes
create_circle(x, y, self.paintcolor, canvas)
self.currentlist.append(listitem)
if len(self.size) == 2:
paintstuff()
# If the canvas is circular, only draw within the circle.
if len(self.size) == 1:
if distance(center, realsizepoint) < (self.size[0] / 2):
paintstuff()
# Using newlist will allow us to keep track of separate lines
# and stop frosting between shapes
def newlist(event):
self.pointslist.append(self.currentlist)
self.currentlist = []
# If the user picks up his/her mouse, start a new list
canvas.bind('<ButtonRelease-1>', newlist)
# Clicking and moving the mouse while clicked draws on the canvas
canvas.bind("<B1-Motion>", paint)
canvas.bind('<Button-1>', paint)
# Set the paint color
def setcolor(color):
self.paintcolor = color
# Set the nozzle type
def setnozzle(shape):
self.nozzletype = shape
# Clear the entire canvas and delete all saved points from lists; redraw
# the circle for the cake if the cake is indeed circular
def clear():
self.pointslist = []
self.currentlist = []
canvas.delete(ALL)
if len(self.size) == 1:
cake = canvas.create_oval(0,0,side,side,fill = self.cakecolor)
# Preview the frosted cake by running through the final list, converting
# from inches back to pixels, and drawing circles and lines on a preview
# canvas accordingly
def preview():
donecake = Tk()
donecake.title('Team Cake')
donecake.resizable(0, 0)
if len(self.size) == 1:
diam = self.size[0]
side = diam*50
palatte = Canvas(donecake, width=side, height=side,
bg = 'black')
cake = palatte.create_oval(0,0,side,side,fill = self.cakecolor)
else:
palatte = Canvas(donecake, width = self.size[0]*50,
height = self.size[1]*50, bg = self.cakecolor)
for linelist in self.pointslist:
palatte.create_oval(linelist[0][0] * 50 - 4,
linelist[0][1] * 50 - 4,
linelist[0][0] * 50 + 4,
linelist[0][1] * 50 + 4,
fill = linelist[0][2],
outline = linelist[0][2])
for i in range(1, len(linelist)):
palatte.create_line(linelist[i - 1][0] * 50,
linelist[i - 1][1] * 50,
linelist[i][0] * 50,
linelist[i][1] * 50,
fill = linelist[i - 1][2], width = 8,
capstyle = ROUND)
palatte.pack()
def frostcake():
"""Converts list to arduino-readable format, parses by nozzle type and
color, and sends commands as needed"""
index = 0
origin = (0, 0)
color = self.pointslist[0][0][2]
nozzle = self.pointslist[0][0][3]
# X- and Y-distances rather than the single absolute distance, for
# which we already have a function
def distances(point1, point2):
distx = point2[0] - point1[0]
disty = point2[1] - point1[1]
dist = (distx, disty)
return dist
def sendcommands(index, origin, color, nozzle):
myCurrentCommands = []
clist = self.pointslist[index]
temppoint = distances(origin, clist[0])
if index == 0:
try:
myCurrentCommands.append(('/,'))
myCurrentCommands.append((0, temppoint[0], temppoint[1]))
except:
print "Failed to send startup key"
else:
myCurrentCommands.append((0, temppoint[0], temppoint[1]))
# Run through the current list, converting points to distances
# and adding a binary indicator of whether the machine is
# frosting, or just moving to begin a new shape
for i in range(1, len(self.pointslist[index])):
point = distances(clist[i - 1], clist[i])
myCurrentCommands.append((1, point[0], point[1]))
index = index + 1
if index == len(self.pointslist):
lastpoint = distances((self.pointslist[-1][-1][0],
self.pointslist[-1][-1][1]),
(0, 0))
myCurrentCommands.append((0, lastpoint[0], lastpoint[1]))
sendtoserial(myCurrentCommands)
return
sendtoserial(myCurrentCommands)
origin = clist[-1]
clist = self.pointslist[index]
# If the color and nozzle type of the next list are the same,
# continue frosting as before. If they have changed, create
# a pop-up message asking the user to change them and waiting
# until the user confirms before continuing to send commands
if clist[0][2] == color and clist[0][3] == nozzle:
sendcommands(index, origin, color, nozzle)
else:
def dothis():
change.destroy()
sendcommands(index, origin, clist[0][2],
clist[0][3])
change = Tk()
change.title('Team Cake')
change.resizable(0, 0)
canvas = Canvas(change, width = 300, height = 80)
canvas.create_text((150, 50), text = "The color or nozzle \
needs changing. \n Please make the frosting color %s. \n Please make the \
nozzle shape a %s. \n When you have done so, press the button." \
%(clist[0][2], clist[0][3]),
width = 300)
quitbutton = Button(change, text = 'The color and nozzle \
are correct. Keep frosting', command = dothis)
canvas.grid(padx = 5)
quitbutton.grid(pady = 10, padx = 5)
def sendtoserial(CCommands):
"""Sends commands to the arduino"""
try:
print "Trying..."
arduino = serial.Serial('COM3', 9600)
print arduino.readline()
except:
print "Failed to connect."
for i in CCommands:
if len(i) == 2:
try:
print "Trying %s" %i
arduino.write(i)
print arduino.readline()
except:
print "Failed to send the startup command"
else:
print "Trying yn...."
print "sending %i" % (i[0])
try:
arduino.write("%i" % (i[0]))
print arduino.readline()
except:
print "Failed to send yn!"
print "Trying x...."
print "sending %i" % (i[1]*1000)
try:
arduino.write("%i" % (i[1] * 1000))
print arduino.readline()
except:
print "Failed to send x!"
print "Trying y...."
print "sending %i" % (i[2]*1000)
try:
arduino.write("%i" % (i[2] * 1000))
print arduino.readline()
except:
print "Failed to send y!"
sendcommands(index, origin, color, nozzle)
print "Frosting complete!"
# Create all color buttons for the paint GUI
red = Button(bg = 'red', command = Callable(setcolor, 'red'))
blue = Button(bg = 'blue', command = Callable(setcolor, 'blue'))
green = Button(bg = 'green', command = Callable(setcolor, 'green'))
white = Button(bg = 'white', command = Callable(setcolor, 'white'))
yellow = Button(bg = 'yellow', command = Callable(setcolor, 'yellow'))
clearbutton = Button(text = 'Start over.', command = clear)
# Create all nozzle buttons for the paint GUI
circlepic = PhotoImage(file = "circlenozzle.gif", master = root)
circbutton = Button(root, image = circlepic,
command = Callable(setnozzle, 'circle'))
circbutton.image = circlepic
diamondpic = PhotoImage(file = "twodiamond.gif", master = root)
diamondbutton = Button(root, image = diamondpic,
command = Callable(setnozzle, 'diamond'))
diamondbutton.image = diamondpic
toothedmoonpic = PhotoImage(file = "toothedmoon.gif", master = root)
toothedbutton = Button(root, image = toothedmoonpic,
command = Callable(setnozzle, 'toothedmoon'),)
toothedbutton.image = toothedmoonpic
moonpic = PhotoImage(file = "moonnozzle.gif", master = root)
moonbutton = Button(root, image = moonpic,
command = Callable(setnozzle, 'moon'))
moonbutton.image = moonpic
bigstarpic = PhotoImage(file = "bigstar.gif", master = root)
bigstarbutton = Button(root, image = bigstarpic,
command = Callable(setnozzle, 'bigstar'))
bigstarbutton.image = bigstarpic
smallstarpic = PhotoImage(file = "smallstar.gif", master = root)
smallstarbutton = Button(root, image = smallstarpic,
command = Callable(setnozzle, 'smallstar'))
smallstarbutton.image = smallstarpic
# Create preview and frost buttons for the paint GUI
previewcanvas = Button(text = 'Preview this cake.',
command = preview)
frostcanvas = Button(text = 'Frost this cake.', command = frostcake)
# Fit all of the paint GUI buttons into the appropriate grid
red.grid(column = 0, row = 0, sticky = N + W + E + S)
blue.grid(column = 1, row = 0, sticky = N + W + E + S)
white.grid(column = 0, row = 1, sticky = N + W + E + S)
green.grid(column = 1, row = 1, sticky = N + W + E + S)
yellow.grid(column = 0, row = 2, sticky = N + W + E + S)
clearbutton.grid(column = 1, row = 2, sticky = N + W + E + S)
circbutton.grid(column = 0, row = 3, sticky = N + W + E + S)
diamondbutton.grid(column = 1, row = 3, sticky = N + W + E + S)
toothedbutton.grid(column = 0, row = 4, sticky = N + W + E + S)
moonbutton.grid(column = 1, row = 4, sticky = N + W + E + S)
bigstarbutton.grid(column = 0, row = 5, sticky = N + W + E + S,
rowspan = 2)
smallstarbutton.grid(column = 1, row = 5, sticky = N + W + E + S,
rowspan = 2)
canvas.grid(column = 2, row = 0, columnspan = 2, rowspan = 6)
previewcanvas.grid(column = 2, row = 6, sticky = W + E + S)
frostcanvas.grid(column = 3, row = 6, sticky = W + E + S)
root.grid_columnconfigure(0, minsize = 50, weight = 1)
root.grid_columnconfigure(1, minsize = 50, weight = 1)
###----BELOW IS THE SIZE-SPECIFICATION GUI, WHICH LAUNCHES THE PAINT GUI------
# Create a warning pop-up message is the user-inputed size exceeds the maximum
# cake size our machine can handle
def sizewarning():
warn = Tk()
warn.title('Team Cake')
warn.resizable(0,0)
canvas = Canvas(warn, width = 300, height = 80)
canvas.create_text((150, 50), text = "We're sorry. Cakes have a \
maximum length of 13 inches, a maximum width of 11 inches, and a maximum \
diameter of 11 inches. Please enter valid cake dimensions.", width = 280)
quitbutton = Button(warn, text = "Let me try again.",
command = warn.destroy)
canvas.grid()
quitbutton.grid(pady = 10)
def close_window():
canvas.destroy()
# Get correct size, regardless of whether the cake is rectangular or circular
def buttonstuff():
size = []
if v1.get() == 0:
if lengthentry.get() == '' or widthentry.get() == '':
sizewarning()
else:
length = int(lengthentry.get())
width = int(widthentry.get())
size = [length, width]
elif v1.get() == 1:
if diameterentry.get() == '':
sizewarning()
else:
diameter = int(diameterentry.get())
size = [diameter]
# We like inches. So we're converting to them if the user was silly enough
# to use centimeters.
if v2.get() == 1:
for i in range(len(size)):
size[i] = size[i] / 2.54
#Check to make sure that they entered suitable dimensions. If not, raise
#a warning, otherwise, close the selection menu and make a paint GUI
if len(size) == 2:
if size[0] > 13 or size[1] > 11:
sizewarning()
else:
g.destroy()
cake = Mycake(size, cakecolor)
cake.PaintGUI()
elif len(size) == 1:
if size[0] > 11:
sizewarning()
else:
g.destroy()
cake = Mycake(size, cakecolor)
cake.PaintGUI()
# Set the default color for the cake color drop-down menu and do sketchy things
# (yes, this involves a global variable) to make the menu work
cakecolor = '#F6F6B1'
menutitle = 'Select a color.'
def setcake(color, title):
global cakecolor
cakecolor = color
menumb.configure(text = title)
# Make the size selection GUI
g = Tk()
g.title('Team Cake')
g.resizable(0,0)
# Make all labels to go in GUI
label1 = Label(text = 'Please enter the dimensions of your cake.')
label2 = Label(text = 'Please choose your units.')
label3 = Label(text = 'Length:')
label4 = Label(text = 'Width:')
label5 = Label(text = 'Diameter:')
label6 = Label(text = 'What color is your cake?')
blank = Label(text = ' ')
# Make all text-entry fields to go in GUI
lengthentry = Entry(g, width = 5)
widthentry = Entry(g, width = 5)
diameterentry = Entry(g, width = 15)
# Make all radiobuttons to go in GUI
v1 = IntVar()
v2 = IntVar()
rectchoice = Radiobutton(text = 'Rectangle', variable = v1, value = 0)
circchoice = Radiobutton(text = 'Circle', variable = v1, value = 1)
incheschoice = Radiobutton(text = 'in', variable = v2, value = 0)
centchoice = Radiobutton(text = 'cm', variable = v2, value = 1)
# Make the "I'm ready" button to go in GUI
button = Button(text = "I'm ready to design an amazing cake.",
command = buttonstuff)
# Make the cake color selection drop-down menu to go in GUI
menumb = Menubutton(text = menutitle, relief = 'raised', bd = 2)
menumb.menu = Menu(menumb, tearoff = 0)
menumb["menu"] = menumb.menu
menumb.menu.add_command(label = 'Yellow',
command = Callable(setcake, '#F6F6B1', 'Yellow'))
menumb.menu.add_command(label = 'Chocolate',
command = Callable(setcake, 'tan4', 'Chocolate'))
menumb.menu.add_command(label = 'White',
command = Callable(setcake, 'white', 'White'))
# Lay out all items in GUI in a grid
label1.grid(row = 0, column = 0, columnspan = 6, pady = 8)
rectchoice.grid(row = 1, column = 0, sticky = W, padx = 2, pady = 4)
blank.grid(row = 1, column = 1)
label3.grid(row = 1, column = 2)
lengthentry.grid(row = 1, column = 3, padx = 4)
label4.grid(row = 1, column = 4)
widthentry.grid(row = 1, column = 5, padx = 4)
circchoice.grid(row = 2, column = 0, sticky = W, padx = 2, pady = 2)
label5.grid(row = 2, column = 2)
diameterentry.grid(row = 2, column = 3, columnspan = 3, sticky = E + W,
padx = 4)
label2.grid(row = 3, column = 0, columnspan = 3, pady = 6)
incheschoice.grid(row = 3, column = 3)
centchoice.grid(row = 3, column = 4)
label6.grid(row = 4, column = 0, columnspan = 3)
menumb.grid(row = 4, column = 3, columnspan = 3, padx = 6, pady = 6,
sticky = N + E + S + W)
button.grid(row = 5, column = 0, columnspan = 6, pady = 6,
padx = 6, sticky = W + E)
# Start doing things!
g.mainloop()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment