Turtle Graphics

From XPUB & Lens-Based wiki

see Category:Turtle Graphics

Turtle graphics refers to a technique of allowing graphics to be drawn, typically on a computer screen, by controlling a virtual "turtle" with simple commands like forward and turn. Turtle graphics were first described using the programming language LOGO, and were created by Seymour Papert as part of constructivist approach to teaching kids geometry by creating simulation environments or "microworlds", where principles could be approached and explored in a direct way, as opposed to classical formal education using pre-determined abstractions.

Turtle graphics are a kind of vector-based graphics system, in contrast to a traditional systems raster-based system like a bitmap.

http://www.greenteapress.com/thinkpython/swampy/

see Vector graphics

Prototyping Assignment

Due date: Tuesday 14 June 2011

Create a drawing using set of cascading functions, as in the examples "arc, leaf, flower" or "building, city, starcity" worked through in class, and the exercises of Chapter 4 of Think Python.

Use the "Seymour" Inkscape extension to create a single SVG file containing your code + the resulting graphics arranged on the page.

Download and try Image:City.svg in Inscake for an example (this file doesn't display properly in the browser, but that's ok, open it in Inkscape and Run the Seymour extension. Right click and use "Save As..." to save the SVG file on your computer.)

Turtle Graphics Inkscape Extension

Installation

Unzip the two files contained in: File:Seymour.zip and copy them to ~/.config/inkscape/extensions[1]. Restart Inkscape if necessary.

Use

If properly installed, you should have a "Python" option in your Extensions menu. Select "Seymour..." to use the extension. Place your code in a text box on the SVG page and right-click the text to select "Object Properties" where you can set the id of the element to "code" (the value the Seymour extension expects).

Summary of turtle commands

Some commands have multiple versions (both fd and forward do the same thing)

  • fd(s), forward(s): Go forward s steps
  • bk(s), backward(s): Go backward s steps
  • lt(d), left(d): Turn left d degrees
  • rt(d), right(d): Turn right d degrees
  • pd(), pendown(): Pen down (and start recording an SVG path)
  • pu(), penup(): Pen up (and draw the SVG path)
  • goto (x, y): Put the turtle at exactly this position (absolute)
  • face(d): Turn the turtle to face this direction (absolute)

Some extra's:

  • startgroup([id]): start an SVG group element (g)
  • endgroup(): end the current group element and record to the page
  • rgb(r, g, b): returns a color value based on red, green, and blue values from 0-255 each.

Styles

  • styles: Is a a python dictionary and can be used to add/modify SVG style values. The current styles are actually used / recorded when you perform a penup (pu) command.

example:

 styles['fill'] = rgb(128, 128, 0)

Code

seymour.py

#!/usr/bin/env python
#-*- coding:utf-8 -*-
"""
==|||+++__<z1*XZY++"|!`+<|=___:+!3X##ZZo3osi_.  :..   -.:.-.. ........---..:.   
=|>-=|i|<nqp2*}:.%><i;+|;:;<1vXXSYXmZ##mqXSoSas_=>:  .._.::. ::........:...:    
=||=|=<amXnc}si=s,is=; %suWB#gawaa21SSXX#XZXXXoal||iil>:=_==:. ..............   
.:+<+Il*1i%=<<owX2v+=a#ZSYVY1UA#U$W#XapZenZXXdZXXmXXooa>=,_.  -...... ......    
;===iz{il%viawmm#}%%JmeozvX#mmmmX#XXXXXXZZXoSXXXmX#m##Xe|+=.:. ..-.. .... ...   
=|=%|i%iInnnwwdQ2nv=ZZowmWWZ##ZSv1vvlilIvvvI1II12XXXXZXoi.. . . .... .......    
:={|<vnnXXmmmqW$mZvv#omQm#ZXZX11ii|||||+|=+i%}*|iivvnXmZXo>.. ......... . ..    
==|=vnoodXoX##audpod&mWW##XXo1li>+|====;=<>+==<|l=i+|iX#ZUXa;..  .  .... . ..   
:=%vsad#mmmB$8XXWhX#Z#B#ZZXoII|==+==;=>++:==>+====|+==|3##XXz|:... ... .. ..    
.ilo21YYZ#m#D2mmDXmQm##ZZX2ov|i+||==+=:;;i+(====>|.=vnooSZZZous==.. . .. . ..   
iIISo#ZZAYXoXXY!qmWWW#XXXXv%l>|====||>>+|====>|=<inXS111ldmXSX%s;+.:  . ....    
S1e}++sa><uenwmWWQWQWmZSSov1i|<>|=====|==:=++==ioXI*+++==+3mZ#oav;;:;... . .    
X2<_owoZcv+mm$WWBmWQW&ZZe1n%iii=|++==+=;==+==||Ii+=;======<V##Xoov%;==:-.. ..   
S(|oXnXX2n>$#qWW#mWBXd2vu1i%l+|=|=+=+=|==||||||+::;;=%wona>=$m#ZS1nai|=::.-..   
vnvoSoXXXnXmm#ZXm##XXn21u1i>|||+<uoool||ii|||>=;:;=<oXmm1vc+)$mmX1XoXs%>;::..   
oXonnSdXoXS#mq#mUX#X2on1vIiasaaoqXY*i+--+=+|=+====|vi=!"X%|==)QWQQmZZXo2s;;::.  
S2XXoo3ooZSmmmm#mZZX221unoXXSX21*++=;::::;:==+:=||swauI}-;:===)QWBm#Zoonvoa,;.  
voXoX2XXSoWWmmmm##ZXXXoo2XX21ii||+===+|||=||l%|=|%nvvi>==+=====]QWm##mmociI1oi_.
v3n2odX2dmmmm###ZZXZo2ood1lii|||saauoav%ioooon>===||i|=====|=;=;3mm####X2n%|i+<%
iunnXXnmXm#mBmmZZZZZX22o2vi|||i321#QZqvwwuXSnvi+=;;::=||+||===;;=VWmZmXXo2Xzi:=)
iI13voXZ#XU#mm##m#ZX2Xoonvi||voS|+!!{X11In21vI>|+==;==:======;:;:=X#W#moo2Svnvs,
vnvZoZ#UX##Z###mm#XXSooonIii|nXouvv}|++i|llIl%i|i>|++=======:::::=]WmZUXpu+lvIli
%oXoXXSX####mZWWW#mX2ooovvliiiIli|+=+====Iliiiiili|>+===;:={|>::;;<dmW##mqaini+{
vnXdXdowZXmmmmmmWW#mXXooovIl|iii|||=====ii|i|ivvvv|======;=<vva===<3m#W#m#1>"s:+
|3nXod#Sm#m#mmWmWWWmmXo2ovIii|||||+=||ii|||+<oo1li||||+=--:mmmZmo>|l#Q##WZ2c|v.=
=vnnXXZZdXmWWWmmQWmWQm#Xoonlli||||||<=+|||=;IXS||||||||;=<yQQWWUmmciXm#WmXoo|=  
=onnX2SX###W#WWWWmQQQQQmXXovvii|||||+|=+;==ilvnnawmmmwaauQWWQWU#Q#GvXWWWmm#p=|:_
|1nvonXXdX####mWWQQQQQQWmZSovvliii||+++++=|i2ZmXmQWWQQWQQW#WDSm2XXudZQWWmmmXXi. 
::v2vdXXXX####W$WQWQWWWQQQmXXvvvlii||||+++<vqXm#mWWWQQB#Z|iyW#e1InXm#QWQWQZXYv1<
 :<lu{oX1#X#m#$#QWWQQQQQWWW#moovvvlli|=++=vdZmWWWW#UZTmmwdQW#X1sinomBWQWWW#X1|lo
..:vvoZln2XXmXZW##WQQWQQQQWQmmXXnvvllli||iommWWW##SqyWmm#W#XS1liivmmQWQQW#WZS|vo
 -+|l3niu2oX#XZ$WmQmWWQWWQWWWWWmmXXnonvivvXXWB#B$Z###XXXXX11%{uqon#WWQW#mWZ#S2nX
  :=|vnv1nSXZ2Z#WWm$QmQQQWQQQQWWmmZmXoonnoom##WXn2XnSnSonvlaumQZSommWQ#Q##XXnIvX
 =<1svIvi3vdXoX##WWQ$QmQWQQQQQQQQWm#m#UXSXmXX##XXvIvIvvvoXX#WQX2XZmQWmW#XXoSu|]#
  -=:-:<>dS12Z3XmWWWQZmmQQQQQQQQQQQQWmm#m#mZmm##oXvonIvv2XoZW#qmmWQQ#md##X1)na%d
  :--.=iidsInvXZ#WWQWQZWQQWWQQQQWQWQWQQQWWmWBQWmQmmmoX2oooX####QWQQQQWmX2Svc+!3S
     |ii%1szXvdXmmQWQQQ#QWWQQQQQWQQQQQQQQWQWmWWQWW#W#m#mQmQ#QmQWWQQQWQm#Xv==:..:
      .I{vXXvnXX#$QWmW#WWBQWWWWQWQQQQQQQQQQQQWWWW#WWWmWmmQWWWWWWQQWWWmZZZzni|=:.
      =|<12XzoXX#mm#X#mYS###QQWWQQQQQQQQQQQWQQQmWQmQQWW#mQWBWWQQQQW$#mweXc"i= ..
     :;.:|InnnnXoXXW##XS1nX#XZ##QQQWQQWQQQQQQQmWQ$mm$WWZ#m#W$#QWW#Z#ZXS2i|--   <
"""

import random, lxml, sys
sys.path.append('/usr/share/inkscape/extensions') # or another path, as necessary
import inkex
from simplestyle import *

#########################################################
# lxml utils

def collecttext (elt):
    ret = elt.text or ""
    ret += "\n".join([collecttext(child) for child in elt])
    return ret

#def getcontents (element):
#    return (element.text or "") + "".join(map(lxml.etree.tostring, element))

#########################################################
# the main turtle action

import math

defaultpathstyles = {
    'fill':'none',
    'fill-rule':'evenodd',
    'stroke':'#000000',
    'stroke-width':'1px',
    'stroke-linecap':'butt',
    'stroke-linejoin':'miter',
    'stroke-opacity':'1'
}

def rgb (r, g, b):
    return '#%02x%02x%02x' % (r,g,b)

# <path style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="%s" />
def pointstodata (points):
    try:
        data = "M %0.6f,%0.6f" % points[0]
        for p in points[1:]:
            data += " L %0.6f,%0.6f" % p
        return data
    except IndexError:
        return ""

class Turtle (object):
    def __init__(self, x=100.0, y=100.0, heading=0.0, pendown=False, layer=None):
        self.x = x
        self.y = y
        self.heading = heading
        self._pendown = pendown
        self.points = []
        self.stack = []
        if layer is not None:
            self.stack.append(layer)
        self.styles = {}

    def goto(self, x, y):
        self.x = x
        self.y = y
    
    def face(self, d):
        self.heading = (d/180.0)*math.pi

    def forward (self, steps):
        self.x += math.cos(self.heading) * steps
        self.y -= math.sin(self.heading) * steps
        if self._pendown:
            self.points.append((self.x, self.y))

    def left (self, d):
        d = (d/180.0)*math.pi
        self.heading += d

    def backward (self, steps):
        self.forward(-steps)

    def right (self, d):
        self.left(-d)

    def backward (self, steps):
        self.bk(steps)

    def pendown (self):
        self._pendown = True
        self.points.append((self.x, self.y))
        
    def penup (self):
        self._pendown = False
        if len(self.points) and len(self.stack):
            path = inkex.etree.Element(inkex.addNS('path', 'svg'))
            # http://stackoverflow.com/questions/1551666/how-can-2-python-dictionaries-become-1/1551878#1551878
            styles =  dict(defaultpathstyles, **self.styles)
            path.set('style', formatStyle(styles))
            path.set('d', pointstodata(self.points))
            self.stack[-1].append(path)
            self.points = []

    def startgroup (self, id=None):
        g = inkex.etree.Element(inkex.addNS('g', 'svg'))
        if id is not None:
            g.set('id', str(id))
        self.stack[-1].append(g)
        self.stack.append(g)

    def endgroup (self):
        self.stack.pop()

    ## aliases
    def fd (self, steps): self.forward(steps)
    def bk (self, steps): self.backward(steps)
    def lt (self, d): self.left(d)
    def rt (self, d): self.right(d)
    def pd (self): self.pendown()
    def pu (self): self.penup()

    def export_to_dict (self, d):
        d['forward'] = self.forward
        d['backward'] = self.backward
        d['left'] = self.left
        d['right'] = self.right
        d['pendown'] = self.pendown
        d['penup'] = self.penup
        d['goto'] = self.goto
        d['face'] = self.face
        d['startgroup'] = self.startgroup
        d['endgroup'] = self.endgroup
        d['fd'] = self.fd
        d['bk'] = self.bk
        d['lt'] = self.lt
        d['rt'] = self.rt
        d['pd'] = self.pd
        d['pu'] = self.pu
        d['styles'] = self.styles

#########################################################
# The Inkscape Effect interface

class SeymourEffect(inkex.Effect):
    def __init__(self):
        inkex.Effect.__init__(self)
        # http://docs.python.org/lib/module-optparse.html
        self.OptionParser.add_option('-c', '--codeid', action = 'store',
          type = 'string', dest = 'codeid', default = 'code',
          help = 'ID of code element')

    def effect (self):
        codeid = self.options.codeid
        svg = self.document.getroot()
        # svg = self.document.xpath('//svg:svg', namespace = inkex.NSS)[0]

        code = self.document.xpath("//*[@id='%s']" % codeid)[0]
        src = collecttext(code)
        # print src
        
        width = inkex.unittouu(svg.get('width'))
        height = inkex.unittouu(svg.attrib['height'])
        outputlayer = inkex.etree.SubElement(svg, 'g')
        outputlayer.set(inkex.addNS('label', 'inkscape'), 'Seymour Output')
        outputlayer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
        
        turtle = Turtle(x=width/2, y=height/2, layer=outputlayer)
        d = {}
        turtle.export_to_dict(d)
        d['rgb'] = rgb
        exec src in d

##############################################################################
# This file can be directly applied to an SVG on the commandline using:
# python seymour.py path/to/your.svg

if __name__ == "__main__":
    effect = SeymourEffect()
    effect.affect()

seymour.inx

<inkscape-extension>
  <_name>Seymour</_name>
  <id>org.automatist.filter.seymour</id>
  <dependency type="executable" location="extensions">seymour.py</dependency>
  <dependency type="executable" location="extensions">inkex.py</dependency>
  <param name="codeid" type="string" _gui-text="ID of code element">code</param>
  <effect>
    <object-type>all</object-type>
    <effects-menu>
       <submenu _name="Python"/>
    </effects-menu>
  </effect>
  <script>
    <command reldir="extensions" interpreter="python">seymour.py</command>
  </script>
</inkscape-extension>

Examples

for i in range(45):
  pd()
  fd(100+i*10)
  lt(45)
  styles['stroke-width'] = i
  pu()

InkscapeSeymourSpiral.svg