ubergeekism/lindenmayer.py
2011-01-20 23:22:12 +01:00

196 lines
5.7 KiB
Python

from collections import deque
class IndexedGenerator(object):
"""Add a way to get a generator item by its index"""
def __init__(self, generator):
self.generator = generator
self.cache = []
def __getitem__(self, index):
for i in xrange( index - len(self.cache) + 1 ):
self.cache.append( self.generator.next() )
return self.cache[index]
class LindenmayerSystem(IndexedGenerator):
"""Base virtual class for a Lindenmayer system"""
def __init__(self, axiom, rules, angle, heading=0):
self.angle = angle
self.heading = heading
self.states = deque()
self.actions = {
'F': self.forward,
'+': self.right,
'-': self.left,
'[': self.save,
']': self.restore,
}
super(LindenmayerSystem, self).__init__(self.lindenmayer(axiom, rules))
def lindenmayer(self, axiom, rules):
rules = rules.items()
# def inside the lindenmayer function, so as to use "axiom" at instanciation
def apply(axiom, (symbol, replacement)):
return axiom.replace(symbol, replacement.lower())
while True:
yield axiom
axiom = reduce(apply, rules, axiom).upper()
def forward(self):
raise NotImplementedError
def right(self):
raise NotImplementedError
def left(self):
raise NotImplementedError
def save(self):
raise NotImplementedError
def restore(self):
raise NotImplementedError
class TurtleLSystem(LindenmayerSystem):
"""Draw a L-System using the Turtle module"""
def __init__(self, turtle, axiom, rules, angle, heading=0, size=1):
self.turtle = turtle
self.size = size
super(TurtleLSystem, self).__init__( axiom, rules, angle, heading )
def draw(self, depth):
self.turtle.setheading(self.heading)
for char in self[depth]:
if char in self.actions:
self.actions[char]()
def forward(self):
self.turtle.forward(self.size)
def left(self):
self.turtle.left(self.angle)
def right(self):
self.turtle.right(self.angle)
def save(self):
x = self.turtle.xcor()
y = self.turtle.ycor()
h = self.turtle.heading()
self.states.append( (x, y, h) )
def restore(self):
turtle.up()
x, y, h = self.states.pop()
turtle.setx(x)
turtle.sety(y)
turtle.setheading(h)
turtle.down()
class DumpTurtleLSystem(TurtleLSystem):
"""Keep the set of uniques L-System segments drawn by the Turtle"""
def __init__(self, turtle, axiom, rules, angle, heading=0, size=1, rounding=10):
# using a set avoid duplicate segments
self.segments = set()
# nb of significant digits for rounding
self.rounding=10
super(DumpTurtleLSystem, self).__init__( turtle, axiom, rules, angle, heading, size )
def forward(self):
"""Store segment coordinates and do a forward movement"""
# without rounding, there may be the same node with different coordinates,
# because of error propagation
x1 = round( self.turtle.xcor(), self.rounding )
y1 = round( self.turtle.ycor(), self.rounding )
start = ( x1, y1 )
super(DumpTurtleLSystem, self).forward()
x2 = round( self.turtle.xcor(), self.rounding )
y2 = round( self.turtle.ycor(), self.rounding )
end = ( x2, y2 )
self.segments.add( (start,end) )
def draw(self, depth):
"""Call the draw function, then clean the data"""
super(DumpTurtleLSystem, self).draw(depth)
self.clean()
def clean(self):
"""Remove segments that have duplicated clones in the reverse direction
(the segments is a set, that guarantees that no other clone exists)"""
for segment in self.segments:
for start,end in segment:
# FIXME surely faster to catch the exception than to do two search?
if (end,start) in self.segments:
self.segments.remove( (end,start) )
def __str__(self):
dump = ""
for segment in self.segments:
for coords in segment:
for x in coords:
dump += str(x)+" "
dump += "\n"
return dump
def plot_segments( segments ):
import matplotlib.pyplot as plot
from matplotlib.path import Path
import matplotlib.patches as patches
fig = plot.figure()
ax = fig.add_subplot(111)
for segment in segments:
start,end = segment
verts = [start,end,(0,0)]
codes = [Path.MOVETO,Path.LINETO,Path.STOP]
path = Path(verts, codes)
patch = patches.PathPatch(path, facecolor='none', lw=1)
ax.add_patch(patch)
ax.set_xlim(-50,50)
ax.set_ylim(-50,50)
plot.show()
if __name__=="__main__":
import sys
depth = 1
if len(sys.argv) > 1:
depth = int( sys.argv[1] )
segment_size = 10
float_rounding = 10
from turtle import Turtle
turtle = Turtle()
turtle.speed('fastest')
penrose = DumpTurtleLSystem(turtle,
axiom="[X]++[X]++[X]++[X]++[X]",
rules={
'F': "",
'W': "YF++ZF----XF[-YF----WF]++",
'X': "+YF--ZF[---WF--XF]+",
'Y': "-WF++XF[+++YF++ZF]-",
'Z': "--YF++++WF[+ZF++++XF]--XF"
},
angle=36, heading=0, size=segment_size, rounding=float_rounding )
penrose.draw( depth )
#print penrose
#plot_segments( penrose.segments )
import tsplib
tsplib.write_segments( penrose.segments, segment_size, depth, float_rounding, fd=sys.stdout )