Last active
March 17, 2018 13:54
-
-
Save Balletie/3e16f3cb58859d35ce4e7943bc2ca41e to your computer and use it in GitHub Desktop.
LSystems in python with turtle graphics
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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