Skip to content

Instantly share code, notes, and snippets.

@Balletie
Last active March 17, 2018 13:54
Show Gist options
  • Save Balletie/3e16f3cb58859d35ce4e7943bc2ca41e to your computer and use it in GitHub Desktop.
Save Balletie/3e16f3cb58859d35ce4e7943bc2ca41e to your computer and use it in GitHub Desktop.
LSystems in python with turtle graphics
import itertools as it
import functools as ft
import math
import turtle
import tkinter as tk
from PIL import Image, ImageTk
class LSystem(object):
def __init__(self, axioms, rules):
self.axioms = axioms
self.rules = rules
def produce(self, steps=3):
yield from self._recursive_produce(self.axioms, steps)
def _recursive_produce(self, input, steps, level=0):
if level >= steps:
for c in input:
yield c
else:
for pred in input:
succ = self.rules.get(pred, [pred])
yield from self._recursive_produce(succ, steps, level + 1)
class OneLSystem(LSystem):
def _mk_pred(self, left, strict_pred):
if strict_pred is None:
return None
return (left, strict_pred)
def _recursive_produce(self, input, steps, level=0):
if level >= self.steps:
for c in input:
yield c
else:
left = None
for strict_pred in input:
pred = self._mk_pred(left, strict_pred)
if pred is None:
succ = []
else:
succ = self.rules.get(
pred,
self.rules.get(strict_pred, [strict_pred])
)
yield from self._recursive_produce(succ, steps, level + 1)
left = strict_pred
class TwoLSystem(LSystem):
def _mk_pred(self, left, strict_pred, right):
if strict_pred is None:
return None
return (left, strict_pred, right)
def _recursive_produce(self, input, steps, level=0):
if level >= self.steps:
for c in input:
yield c
else:
left = None
strict_pred = None
for right in it.chain(input, [None]):
pred = self._mk_pred(left, strict_pred, right)
if pred is None:
succ = []
else:
succ = self.rules.get(
pred,
self.rules.get(strict_pred, [strict_pred])
)
yield from self._recursive_produce(succ, steps, level + 1)
left = strict_pred
strict_pred = right
peano = LSystem(
axioms='L',
rules={
'L': 'LFRFL-F-RFLFR+F+LFRFL',
'R': 'RFLFR+F+LFRFL-F-RFLFR',
},
)
sierpinski_ah = LSystem(
axioms='YF',
rules={
'X': 'YF+XF+Y',
'Y': 'XF-YF-X',
},
)
dragon_curve = LSystem(
axioms='FX',
rules={
'X': 'X+YF+',
'Y': '-FX-Y',
},
)
hilbert = LSystem(
axioms='L',
rules={
'L': '+RF−LFL−FR+',
'R': '−LF+RFR+FL−',
},
)
sierpinski = LSystem(
axioms='L--F--L--F',
rules={
'L':'+R-F-R+',
'R':'-L+F+L-',
},
)
def empty_hook(*args):
pass
def make_turtle(lsystem, steps=3, angle=math.pi / 2, f_length=0.25,
initial_coordinates=(0,0), initial_angle=0,
prep_canvas=None, prep_turtle=None, post_turtle=None):
if callable(f_length):
f_length = f_length(steps)
if callable(initial_coordinates):
initial_coordinates = initial_coordinates(steps)
if prep_canvas is None:
prep_canvas = empty_hook
if prep_turtle is None:
prep_turtle = empty_hook
if post_turtle is None:
post_turtle = empty_hook
def exec_turtle(t, ts, canvas, char_out):
t.radians()
ts.setworldcoordinates(-0.1, -0.1, 1.1, 1.1)
prep_canvas(canvas)
def _forward():
t.forward(f_length)
def _left():
t.left(angle)
def _right():
t.right(angle)
actions = {
'F': _forward,
'+': _left,
'-': _right,
}
t.penup()
t.setpos(*initial_coordinates)
t.pendown()
t.left(initial_angle)
prep_turtle(t)
for c in lsystem.produce(steps):
action = actions.get(c, None)
char_out.insert(tk.END, c)
if action:
action()
post_turtle(t)
return exec_turtle
def print_result(lsystem):
out = ''
for c in lsystem.produce():
out += c
print(out)
def sierpinski_canvas_prep(canvas):
img = Image.open('munari_small.jpg')
img.thumbnail((375,375), Image.ANTIALIAS)
tkimg = ImageTk.PhotoImage(img)
cimg = canvas.create_image(-22, 22, anchor=tk.SW, image=tkimg, tag="drawing")
canvas.munari_img = tkimg # Needed so that tkimg is not GC'ed
def sierpinski_turtle_prep(t):
t.fillcolor(0, 73, 255)
t.pencolor('white')
t.pensize(4)
t.shape('turtle')
t.speed('normal')
def sierpinski_size(steps):
diag_length_factor = math.sqrt(2) / 2
diag_height = 2 ** int((steps + 1) / 2)
straight_height = 2 ** int(steps/2) - 1
return 1 / (diag_height * diag_length_factor + straight_height)
def sierpinski_coord(steps):
diag_length_factor = math.sqrt(2) / 2
size = sierpinski_size(steps)
return 0, diag_length_factor * size
def peano_size(steps):
lines = 3 ** (steps - 1)
connect_lines = lines - 1
lines *= 2
return 1/(lines + connect_lines)
peano_turtle = make_turtle(
peano, steps=3, angle=math.pi/2,
initial_angle=math.pi/2, f_length=peano_size
)
sierpinski_turtle = make_turtle(
sierpinski,
steps=5, angle=math.pi/4, initial_angle=math.pi/4,
initial_coordinates=sierpinski_coord,
f_length=sierpinski_size, prep_canvas=sierpinski_canvas_prep,
prep_turtle=sierpinski_turtle_prep,
)
turtles = {
"Sierpinski": sierpinski_turtle,
"Peano": peano_turtle,
}
def turtle_app():
root = tk.Tk()
canvas_frame = tk.Frame(root)
canvas = tk.Canvas(canvas_frame, width=400, height=400)
canvas.pack()
canvas_frame.pack()
control_frame = tk.Frame(root)
var = tk.StringVar(control_frame)
var.set("Sierpinski")
options = tk.OptionMenu(control_frame, var, *turtles.keys())
options.pack(padx=10, side=tk.LEFT)
ts = turtle.TurtleScreen(canvas)
ts.colormode(255)
t = turtle.RawTurtle(ts)
def set_turtle():
turtle_system = turtles.get(var.get())
text.delete(1.0, tk.END)
canvas.delete("drawing")
t.reset()
turtle_system(t, ts, canvas, text)
play_button = tk.Button(control_frame, text="Run", command=set_turtle)
play_button.pack(side=tk.LEFT)
control_frame.pack(fill=tk.BOTH, expand=True)
text_frame = tk.LabelFrame(root, text="Draw commands")
text = tk.Text(text_frame)
text.pack()
text_frame.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)
set_turtle()
root.mainloop()
turtle_app()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment