FrontPage
»
ProgrammingForArtists
»
ContagiousScriptsLibrary
»
KutiaPy
#! /usr/bin/python
"""Kutia Turtle graphics program
ToDo for 1.0
============
* Clear updates on quit
* Drag to moveTo
* DragHandle to turn
* CommandWindow
* Change current turtle
* Penup/pendown as radio buttons
* Documentation
* Display current angle and position
Ideas for 2.0
============
Implement turtle language?
Implement turtle communication, conditionals
Implement speed based on distance
Freeze Kutia. Does it work? How big is it?
When move button is used, concatenate adjacent moves into one
What is a "move"? An iteration, a line segment, a turn?
Ideas for 'TurtleTalk' Commands
===============================
Step - run each turtle's next command
Run - run all commands for all turtles in parallel
Forward - move turtle (10, 20, 50, random, specify...)
Turn - (pi/2, pi/3, pi/4, random, specify, change units)
Pen (up/down, color, width, pattern)
Turtle(add, remove, select, hide/show)
Code window for each turtle
Ideas for Turtle Communication
====================
Send message
Share data
Wait for event
Ideas for Turtle Events
=============
Change color
Pen up/down
Arrive/Leave/Pass an area
Intersect a line/color
"""
from math import pi, sin, cos, atan, sqrt, fabs, asin, acos
import whrandom
from Tkinter import *
DEGREE = pi / 180
# test data
house = [
"retVal.append('self.turn(-90)')",
"retVal.append('self.move(100)')",
"retVal.append('self.turn(45)')",
"retVal.append('self.move(75)')",
"retVal.append('self.turn(90)')",
"retVal.append('self.move(75)')",
"retVal.append('self.turn(45)')",
"retVal.append('self.move(100)')"]
star = [
"global star_angle;star_angle = 144",
"for side in range(5): retVal.append('self.move(50); self.turn(star_angle);')"]
circle = [
"global circle_angle;circle_angle = 1",
"for side in range(360): retVal.append('self.move(1); self.turn(circle_angle);')"]
hexagon = [
"global hex_angle;hex_angle = 60",
"for side in range(6): retVal.append('self.move(30); self.turn(hex_angle);')"]
square = [
"global square_angle;square_angle = 90",
"for side in range(4): retVal.append('self.move(30); self.turn(square_angle);')"]
moire = [
"for side in range(360): retVal.append('self.move(100);self.move(-100);self.turn(1);')"]
random = [
"for side in range(60): retVal.append('self.moveRandom(); self.turnRandom();')"]
TRUE = 1
FALSE = 0
class Turtle:
def __init__(self, canvas, name="kutia", commands=[], pencolor='black', x=25, y=15, angle=0.0, speed=4):
self.color = 'dark green'
self.highlight = 'green'
self.outline = 'black'
self.pencolor = pencolor
self.degrees = angle
self.angle = angle * DEGREE
self.speed = speed
self.tagList = [0] * 360
self.cX = x
self.cY = y
self.canvas = canvas
self.visible = TRUE
self.pendown = TRUE
self.name = name
self.commands = self.expand(commands)
self.currentStep = 0
self.draw(0)
self.incrementalTurnFlag = 0 # disable turning animation
def run(self):
self.step();
if not self.doneRunning():
self.canvas.after(1000/self.speed, self.run);
else:
self.currentStep = 0
def hide(self):
self.visible = FALSE
self.hideIcon(int(self.degrees))
def show(self):
self.visible = TRUE
self.showIcon(int(self.degrees))
def doneRunning(self):
if (self.currentStep < len(self.commands)):
return FALSE
return TRUE
def step(self):
#print self.name + " stepping()"
if not self.doneRunning():
exec(self.commands[self.currentStep], globals(), locals())
self.currentStep = self.currentStep + 1
self.canvas.update()
def expand(self, commands):
#print 'Expanding commands for %s' % self.name
retVal = []
if commands:
for line in commands:
exec(line, globals(), locals())
#print 'Done expanding'
return retVal
def hideIcon(self, index):
idx = int(index)
if (self.tagList[idx]):
self.canvas.move(self.tagList[idx], -(self.cX + 30), -(self.cY + 30))
else:
print 'collision in hideIcon()'
def showIcon(self, index):
idx = int(index)
if (self.tagList[idx]):
#print 'showIcon() thinks there is an icon here'
self.canvas.move(self.tagList[idx], self.cX + 30, self.cY + 30)
else:
#print 'showIcon() thinks it needs to draw the icon'
self.draw(idx)
self.currTag = self.tagList[idx]
def move(self, distance):
#print 'cX = ' + str(self.cX) + ', cY = ' + str(self.cY)
newx = self.cX + cos(self.angle) * distance
newy = self.cY + sin(self.angle) * distance
if self.pendown:
height = self.canvas.create_line(self.cX, self.cY, newx, newy, fill=self.pencolor)
self.top(height) # make sure to stay above the new line we draw
self.moveTo(newx, newy)
def moveTo(self, x, y):
#print 'moving from (%d,%d) to (%d,%d)' % (self.cX, self.cY, x, y)
self.canvas.move(self.currTag, x - self.cX, y - self.cY)
self.cX = x
self.cY = y
def moveToPoint(self, x, y):
dX = x - self.cX
dY = y - self.cY
distance = sqrt(dX ** 2 + dY ** 2)
self.turn(self.findAngle(x,y))
self.move(distance)
def turn(self, angle):
#self.turnTo(self.degrees + angle)
inc = 1
if angle < 0: inc = -1
if self.incrementalTurnFlag:
for each in range(int(fabs(angle))):
self.turnTo(self.degrees + inc)
else:
self.turnTo(self.degrees + angle)
def turnTo(self, angle):
self.hideIcon(int(self.degrees))
#print 'turning from %d to %d degrees' % (self.degrees, angle %360)
self.degrees = angle % 360
self.angle = angle * DEGREE
self.showIcon(int(self.degrees))
self.canvas.update()
def moveRandom(self, maxDistance=60):
self.move(whrandom.randint(1, maxDistance))
def turnRandom(self, maxAngle=360):
self.turn(whrandom.randint(1, maxAngle))
def top(self, object):
#self.canvas.tag_raise(self.currTag, object)
pass
def setColor(self, color):
self.pencolor = color
def points(self, coords):
"""Rotate points relative to current orientation"""
cX = self.cX
cY = self.cY
retVal = []
for i in range(len(coords)):
if (i % 2):
pass
else:
x = coords[i]
y = coords[i + 1]
retVal.append(self.convertX(x,y))
retVal.append(self.convertY(x,y))
return retVal
def convertX(self, x, y):
return int(cos(self.angle)*(x - self.cX) - sin(self.angle)*(y - self.cY) + self.cX)
def convertY(self, x, y):
return int(sin(self.angle)*(x - self.cX) + cos(self.angle)*(y - self.cY) + self.cY)
def draw(self, index):
#print 'Pre-drawing image cache %d for %s' % (index, self.name)
#print 'cX = ' + str(self.cX) + ', cY = ' + str(self.cY)
if self.tagList[index]:
print 'collision in draw()'
baseTag = "id" + str(id(self))
#print "generating image %d" % index
self.currTag = baseTag + str(index)
self.tagList[index] = self.currTag
#self.turnTo(index)
self.drawHead()
self.drawTail()
self.drawBody()
self.drawLegs()
#self.hideIcon(i)
#self.turnTo(0)
self.currTag = self.tagList[0]
#self.moveTo(self.cX + 2, self.cY * 2)
# self.showIcon(0)
#print 'Done pre-drawing'
def drawHead(self):
cX = self.cX
cY = self.cY
self.canvas.create_polygon(
self.points((
cX + 15, cY + 2,
cX + 18, cY + 2,
cX + 18, cY + 4,
cX + 20, cY + 4,
cX + 25, cY + 2,
cX + 25, cY - 2,
cX + 20, cY - 4,
cX + 18, cY - 4,
cX + 18, cY - 2,
cX + 15, cY - 2
)),
tags=(self.currTag))
def drawTail(self):
cX = self.cX
cY = self.cY
self.canvas.create_polygon(self.points((cX-15, cY+2,
cX-25, cY,
cX-15, cY)),
tags=(self.currTag))
def drawLegs(self):
cX = self.cX
cY = self.cY
# lower left
self.canvas.create_polygon(self.legCoords(cX-10, cY+8, -1.0, 1.0), tags=(self.currTag))
# upper left
self.canvas.create_polygon(self.legCoords(cX-10, cY-8, -1.0, -1.0), tags=(self.currTag))
# upper right
self.canvas.create_polygon(self.legCoords(cX+10, cY-8, 1.0, -1.0), tags=(self.currTag))
# lower right
self.canvas.create_polygon(self.legCoords(cX+10, cY+8, 1.0, 1.0), tags=(self.currTag))
def drawBody(self):
# draw concentric ovals
cX = self.cX
cY = self.cY
#outer oval
self.canvas.create_polygon(
self.points((cX - 15, cY + 4,
cX - 15, cY - 4,
cX - 8, cY - 10,
cX + 8, cY - 10,
cX + 15, cY - 4,
cX + 15, cY + 4,
cX + 8, cY + 10,
cX - 8, cY + 10
)),
fill=self.highlight,
smooth='true',
outline=self.outline,
tags=(self.currTag))
#inner oval
self.canvas.create_polygon(
self.points((cX-10,cY+2,
cX-10,cY-2,
cX-6,cY-5,
cX+6,cY-5,
cX+10,cY-2,
cX+10,cY+2,
cX+6,cY+5,
cX-6,cY+5)),
fill=self.color,
outline=self.color,
smooth='true',
tags=(self.currTag))
# draw lines on back
self.canvas.create_line(self.points((
cX-9,cY-8,
cX-5, cY-3,
cX-5,cY+3,
cX-9,cY+8)),
smooth='true',
#joinstyle=BEVEL,
width=2,
tags=(self.currTag))
self.canvas.create_line(self.points((
cX+9,cY-8,
cX+5,cY-3,
cX+5,cY+3,
cX+9, cY+8)),
smooth='true',
#joinstyle=BEVEL,
width=2,
tags=(self.currTag))
self.canvas.create_line(self.points((cX, cY-10, cX, cY+10)), width=2, tags=(self.currTag))
self.canvas.create_line(self.points((cX-15, cY, cX+15, cY)), width=2, tags=(self.currTag))
def pack(self):
self.canvas.pack()
def pen_up(self):
self.pendown = 0
def pen_down(self):
self.pendown = 1
def legCoords(self, x, y, dirX, dirY):
leg = (
x,y,
x + 3 * dirX, y + 3 * dirY,
x + 5 * dirX, y + 3 * dirY,
x + 5 * dirX, y + 4 * dirY,
x + 7 * dirX, y + 4 * dirY,
x + 7 * dirX, y,
x + 5 * dirX, y,
x + 5 * dirX, y + 1 * dirY,
x + 4 * dirX, y + 1 * dirY,
x + 2 * dirX, y - 2 * dirY)
return self.points(leg)
def center(self, rect):
self.cX = (rect.bottom - rect.top) / 2
self.cY = (rect.right - rect.left) / 2
def round(self, num):
if num - int(num) >= 0.5: return int(num) + 1
return int(num)
def diffAngle(self, angle):
#print 'Angle: ' + str(angle) + ', current: ' + str(self.degrees)
newangle = angle + self.degrees
if newangle > 360: newangle = newangle - 360
if newangle < -360: newangle = newangle + 360
if newangle > 180: newangle = newangle - 360
if newangle < -180: newangle = newangle + 360
#print 'New angle: ' + str(newangle)
return - newangle
def findAngle(self, x, y):
signx = x - self.cX
signy = y - self.cY
if signx > 0 and signy < 0:
Q = 1
#print "NE"
elif signx < 0 and signy < 0:
Q = 2
#print "NW"
elif signx < 0 and signy > 0:
Q = 3
#print "SW"
elif signx > 0 and signy > 0:
Q = 4
#print "SE"
elif signx == 0 and signy < 0:
#print "N"
return self.diffAngle(90)
elif signx == 0 and signy > 0:
#print "S"
return self.diffAngle(270)
elif signx > 0 and signy == 0:
#print "E"
return self.diffAngle(0)
elif signx < 0 and signy == 0:
#print "W"
return self.diffAngle(180)
len_ac = abs(x - self.cX)
len_bc = abs(y - self.cY)
len_ab = sqrt(len_ac ** 2 + len_bc ** 2)
A = self.round(acos(len_bc / len_ab) / DEGREE)
B = self.round(asin(len_bc / len_ab) / DEGREE)
#print 'A = ' + str(A) + ', B = ' + str(B)
if Q == 1: return self.diffAngle(B)
elif Q == 2: return self.diffAngle(A + 90)
elif Q == 3: return self.diffAngle(B + 180)
elif Q == 4: return self.diffAngle(A + 270)
def testTurtle():
root = Tk()
canvas = Canvas(root)
canvas.pack()
icon = Turtle(canvas,"example")
icon.draw()
icon.move(50,50)
icon.turn(60)
root.mainloop()
class TurtleWindow(Frame):
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.pack()
self.turtles = []
self.currTurtle = None
self.createWidgets()
self.master.title('Kutia')
self.master.iconname('Kutia')
self.demoInitialized = 0
def createWidgets(self):
self.addCanvas()
self.makeMenuBar()
self.makeButtonBar()
self.addTurtle()
#self.addTurtleCommandFrame()
def makeMenuBar(self):
self.menubar = Frame(self, relief=RAISED, bd=2)
self.menubar.pack(side=TOP, fill=X)
self.turtleMenu()
self.preferenceMenu()
self.colorMenu()
self.demoMenu()
def makeButtonBar(self):
toolbar = Frame(self, cursor='hand2', relief=SUNKEN, bd=2)
toolbar.pack(side=TOP, fill=X)
Button(toolbar, text='Pen up', command=self.penUp).pack(side=LEFT)
Button(toolbar, text='Pen down', command=self.penDown).pack(side=LEFT)
Button(toolbar, text='Clear', command=self.clear).pack(side=LEFT)
def turtleMenu(self):
mbutton = Menubutton(self.menubar, text='Turtle', underline=0)
mbutton.pack(side=LEFT)
menu = Menu(mbutton)
self.turtleMenu = menu
mbutton['menu'] = menu
return mbutton
def colorMenu(self):
mbutton = Menubutton(self.menubar, text='Color', underline=0)
mbutton.pack(side=LEFT)
menu = Menu(mbutton)
colors = ('red', 'orange', 'yellow', 'green', 'blue', 'violet', 'black')
for color in colors:
def m_command(self=self, color=color):
self.changeColor(color)
menu.add_command(label=color, command=m_command)
mbutton['menu'] = menu
return mbutton
def changeColor(self, color):
self.currTurtle.setColor(color)
def demoMenu(self):
mbutton = Menubutton(self.menubar, text='Demo', underline=0)
mbutton.pack(side=LEFT)
menu = Menu(mbutton)
menu.add_command(label='Square', command=self.squareDemo)
menu.add_command(label='Hexagon', command=self.hexagonDemo)
menu.add_command(label='House', command=self.houseDemo)
menu.add_command(label='Circle', command=self.circleDemo)
menu.add_command(label='Moire', command=self.moireDemo)
menu.add_command(label='Random', command=self.randomDemo)
menu.add_command(label='Demo All', command=self.demo)
mbutton['menu'] = menu
return mbutton
def preferenceMenu(self):
mbutton = Menubutton(self.menubar, text='Preferences', underline=0)
mbutton.pack(side=LEFT)
menu = Menu(mbutton)
menu.add_command(label='Name turtle...', command=self.doNothing)
mbutton['menu'] = menu
return mbutton
def addCanvas(self):
self.canvas = Canvas(self, bg="white",height=400,width=600)
self.canvas.pack(side=BOTTOM)
self.canvas.bind('<Button-1>', self.turnTo)
def squareDemo(self):
turtle = Turtle(self.canvas, name="black", pencolor='black', x=200, y=200, commands=square, speed=10)
self.turtles.append(turtle)
turtle.run()
self.currTurtle = turtle
def hexagonDemo(self):
turtle = Turtle(self.canvas, name="blue",pencolor='blue', x=300, y=300, commands=hexagon, speed=20)
self.turtles.append(turtle)
turtle.run()
self.currTurtle = turtle
def houseDemo(self):
turtle = Turtle(self.canvas, name="red", pencolor='red', x=100, y=400, commands=house, speed=30)
self.turtles.append(turtle)
turtle.run()
self.currTurtle = turtle
def starDemo(self):
turtle = Turtle(self.canvas, name="yellow", pencolor='yellow', x=400, y=100, commands=star, speed=40)
self.turtles.append(turtle)
turtle.run()
self.currTurtle = turtle
def circleDemo(self):
turtle = Turtle(self.canvas, name="green", pencolor='green', x=100, y=100, commands=circle, speed=1001)
self.turtles.append(turtle)
turtle.run()
self.currTurtle = turtle
def moireDemo(self):
turtle = Turtle(self.canvas, name="orange", pencolor='DarkOrange', x=300, y=100, commands=moire, speed=999)
self.turtles.append(turtle)
turtle.run()
self.currTurtle = turtle
def randomDemo(self):
turtle = Turtle(self.canvas, name="violet", pencolor='violet', x=300, y=200, commands=random, speed=100)
self.turtles.append(turtle)
turtle.run()
self.currTurtle = turtle
def initDemo(self):
if self.demoInitialized: return
# Create some turtles to test
self.turtles.append(Turtle(self.canvas, name="black", pencolor='black', x=200, y=200, commands=square, speed=1))
self.turtles.append(Turtle(self.canvas, name="blue",pencolor='blue', x=300, y=300, commands=hexagon, speed=2))
self.turtles.append(Turtle(self.canvas, name="red", pencolor='red', x=100, y=400, commands=house, speed=3))
self.turtles.append(Turtle(self.canvas, name="yellow", pencolor='yellow', x=400, y=100, commands=star, speed=4))
self.turtles.append(Turtle(self.canvas, name="green", pencolor='green', x=100, y=100, commands=circle, speed=1000))
self.turtles.append(Turtle(self.canvas, name="orange", pencolor='DarkOrange', x=300, y=100, commands=moire, speed=1000))
self.turtles.append(Turtle(self.canvas, name="violet", pencolor='violet', x=300, y=200, commands=random, speed=10))
self.demoInitialized = 1
def demo(self):
self.initDemo()
for turtle in self.turtles:
turtle.run()
def doNothing(self):
pass
def addTurtle(self):
self.turtles.append(self.currTurtle)
self.currTurtle = Turtle(self.canvas, name="Kutia", commands=[], x=100, y=100)
def forward(self):
self.currTurtle.commands.append('self.move(20)')
self.currTurtle.step()
def turnLeft(self):
self.currTurtle.turn(-15)
def turnRight(self):
self.currTurtle.turn(15)
def penUp(self):
self.currTurtle.pen_up()
def penDown(self):
self.currTurtle.pen_down()
def turnTo(self, event):
#print 'x = ' + str(event.x) + ', y = ' + str(event.y)
if self.currTurtle:
self.currTurtle.moveToPoint(event.x, event.y)
def nameTurtle(self):
print 'Name turtle called'
def clear(self):
self.turtles = []
for item in self.canvas.find_all():
self.canvas.delete(item)
self.canvas.update()
self.addTurtle()
def testWindow():
root = Tk()
window = TurtleWindow(root)
root.mainloop()
if __name__=='__main__':
#testTurtle()
testWindow()