This commit is contained in:
2022-01-23 10:10:59 +01:00
parent 7d4884946f
commit bdb11c22d0
4 changed files with 113 additions and 21 deletions

View File

@ -1,2 +1,94 @@
# lindenmayer # Visualisierung von Lindermayer-Systemen
von Marcel Zinkel, 19.01.2022
## Theorie
### formale Definition
Ein Lindenmayer-System (auch L-System) ist ein Quadrupel G=(V,S,$\omega$,P).
- V ist die Menge der Variablen.
- S ist die Menge der Konstanten. Die Mengen V und S bilden das Alphabet.
- $\omega$ ist ein Wort aus Zeichen des Alphabets, das man auch Startwort nennt.
- P ist eine Menge von Paaren von Variablen und Wörtern, die die Ersetzungsregeln
definieren.
### kontextfreie L-Systeme
Beim Erzeugen eines L-Systems wird eine Tiefe n festgelegt, mit der die Ersetzungsschritte
wiederholt werden. Für jedem Ersetzungsschritt wird jedes Zeichen des aktuellen Wortes
$\omega$ durchgegangen und jeweils die Variablen durch das in den Ersetzungsregeln
festgelegt gelegte Wort ersetzt. Die Konstanten werden nicht ersetzt.
### Visualisierung
Eine übliche Visualisierung besteht darin, dass eine Figur z. B. eine Schildkröte
in Python über den Bildschirm bewegt wird. Folgende Zeichen haben dabei eine besondere
Bedeutung:
- F: Bewegung nach vorne um eine bestimmte Länge und Zeichnen einer Linie
- +: Drehung im mathematisch positiven Sinne um den Winkel $\alpha$
- -: Drehung im mathematisch negativen Sinne um den Winkel $\alpha$
- [: speichert die Position und Rotation auf einem Stack
- ]: stellt die oberste Position und Rotation des Stacks wieder her
## Implementierung
Das Programm wird in Python implementiert, wofür mindestens die Version 3.10 benötigt
wird, weil die match-case-Anweisung verwendet wird, die in dieser Version eingeführt
wurde. Als Abhängigkeit wird das pip-Paket pillow benötigt. Dies kann mit
`pip install .` installiert werden. Optional für das Exportieren als Bild wird
[Ghostscript](https://www.ghostscript.com/) benötigt.
Alle Schildkröten werden mit der `newTurtle` Funktion in der `drawer.py`
erstellt. Diese sorgt dafür, dass alles so schnell wie möglich gezeichnet wird
und die Schildkröte nicht zu sehen ist.
Die Klasse `Drawer` in der `drawer.py` ist am wichtigsten, da diese die
L-Systeme zeichnet. Die `draw` Methode dieser Klasse geht Zeichen für Zeichen
des Wortes, mit der sie aufgerufen wurde, durch. Bei + oder - wird die
Schildkröte nach links bzw. rechts um einen zuvor festgelegten Winkel gedreht.
Bei F wird sie nach vorne bewegt. In der Liste `turtleStates` wird der Zustand
der Schildkröte, also die Position und Rotation, gespeichert. Bei [ wird wird
der Zustand am Ende der Liste gespeichert und bei ] der letzte Zustand der Liste
wiederhergestellt und anschließend gelöscht. Außerdem wird für Zeichen, für die
es eine Ersetzungsregel gibt, die `draw` Methode rekursiv mit dem der
Ersetzungsregel entsprechenden Wort aufgerufen.
Die Klasse `DrawerSimulation` erbt von `Drawer` und simuliert ein L-System und
ermittelt Bereich, indem es gezeichnet werden würde. So kann die `draw` Funktion
in der `drawer.py` erst das L-System mithilfe von `DrawerSimulation` simulieren.
Anschließend wird die Startpostion der Schildkröte, sowie Länge mit der sie sich
fortbewegt, so gewählt, dass sich die Zeichnung im vorgesehenen Bereich
befindet. Mit diesen Werten wird es mit der Klasse `Drawer` gezeichnet.
Schließlich werden die Einstellungen, mit denen das L-System gezeichnet wurde,
in der Liste `drawings` abgespeichert.
In der `__main__.py` werden zunächst einige Einstellungen vorgenommen und die
Funktion `takeInput` in `inputHelper.py` aufgerufen. Diese nimmt einen Befehl
entgegen und ruft sich danach rekursiv auf. Beim d(raw) Befehl wird nach dem zu
zeichnenden L-System und verschiedenen Einstellungen gefragt. Dazu werden
diverse Hilfsfunktionen für unterschiedliche Eingabetypen z. B. Zahlen und
Zeichenketten benutzt. Zuletzt wird gefragt, ob das L-System das ganze Fenster
ausfüllen soll. Wird dies verneint, kann der Nutzer durch Klicken auf die
diagonalen Eckpunkte, die Position und Größe der Zeichnung eingeben. Schließlich
wird die `draw` Funktion aufgerufen.
Eine Zeichnung kann auch wieder mit der `delete` Funktion der `drawer.py`
gelöscht werden, da jedes L-System mit einer neuen Schildkröte gezeichnet wird.
So kann eine Zeichnung durch den Aufruf der `clear` Methode der Schildkröte
gelöscht werden.
Außerdem kann der Zustand des Programms mit allen Zeichnungen und der gewählten
Hintergrundfarbe abgespeichert und wieder geladenen werden. Hierfür wird die in
Python integrierte pickle Library verwendet.
Das Bild kann auch als Bilddatei exportiert werden. Dafür wird eine Funktion von
Tk benutzt, mit der man das Bild im Postscript Format erhält. Mit der PIL kann es
dann in verschiedensten Bildformaten abgespeichert werden.
## Quellen
[https://de.wikipedia.org/wiki/Lindenmayer-System](https://de.wikipedia.org/wiki/Lindenmayer-System),
aufgerufen am 14. Januar 2022 um 16:12 Uhr

View File

@ -17,11 +17,11 @@ screen.colormode(255) # ändert den Farbmodus auf RGB mit 24 Bits
screen.getcanvas().winfo_toplevel().protocol("WM_DELETE_WINDOW", quit) screen.getcanvas().winfo_toplevel().protocol("WM_DELETE_WINDOW", quit)
try: try:
inputHelper.takeInput(turtleObject) inputHelper.takeInput(turtleObject)
# Die Python integrierte "input"-Funktion wirft diesen Fehler, wenn der Nutzer die Tastenkombi Strg-D # Die Python integrierte "input"-Funktion wirft diesen Fehler, wenn der Nutzer die Tastenkombination Strg-D
# auf einem Unix-System verwendet. Es ist üblich, dass das Programm daraufhin beendet wird. # auf einem Unix-System verwendet. Es ist üblich, dass das Programm daraufhin beendet wird.
# Wir folgen dieser Konvention und beenden das Programm wobei die Fehlermeldung abgefangen wird, # Wir folgen dieser Konvention und beenden das Programm wobei die Fehlermeldung abgefangen wird,
# damit der Nutzer nicht von einer Fehlermeldung verwirrt wird, wenn dieser das Programm absichtlich # damit der Nutzer nicht von einer Fehlermeldung verwirrt wird, wenn dieser das Programm absichtlich
# beendet. EOF-Fehler von anderen Ein- oder Augabeoperationen werden bereits voher abgefangen. # beendet. EOF-Fehler von anderen Ein- oder Ausgabeoperationen werden bereits vorher abgefangen.
except EOFError: except EOFError:
quit() quit()
screen.mainloop() # führt den Tk-mainloop aus screen.mainloop() # führt den Tk-mainloop aus

View File

@ -36,7 +36,7 @@ class Drawer():
self.forwardDistance = drawing.info.scale self.forwardDistance = drawing.info.scale
self.turtle = drawing.turtle self.turtle = drawing.turtle
# bewegt die turtle zur Sartposition und rotiert die turtle # bewegt die turtle zur Startposition und rotiert die turtle
# Zuvor wird das Zeichnen temporär deaktiviert, damit die turtle beim Bewegen # Zuvor wird das Zeichnen temporär deaktiviert, damit die turtle beim Bewegen
# zur neuen Position keine Linie zeichnet. # zur neuen Position keine Linie zeichnet.
self.turtle.penup() self.turtle.penup()
@ -50,16 +50,16 @@ class Drawer():
def storeEdges(self): def storeEdges(self):
pass pass
# wrappt die pendown Methode der turtle, damit das zeichnen bei überschreiben der Methode # wrappt die pendown Methode der turtle, damit das Zeichnen bei Überschreiben der Methode
# deaktiviert werden kann. # deaktiviert werden kann.
def pendown(self): def pendown(self):
self.turtle.pendown() self.turtle.pendown()
# Die Methode zeichnet das Lindenmayer-System. # Die Methode zeichnet das Lindenmayer-System.
# n gibt die (verbleibende) Rekursiontiefe an. # n gibt die (verbleibende) Rekursionstiefe an.
# Beim ersten Aufrufen wird die Methode ohne Paramenter aufgerufen. # Beim ersten Aufrufen wird die Methode ohne Parameter aufgerufen.
def draw(self, word = None, n = None): def draw(self, word = None, n = None):
# weist die Standartwerte zu # weist die Standardwerte zu
if n == None: if n == None:
n = self.recursionDepth n = self.recursionDepth
if word == None: if word == None:
@ -124,9 +124,9 @@ class DrawerSimulation(Drawer):
pass pass
# Die Funktion simuliert erst das Lindenmayer-System, um die Größe zu erhalten. # Die Funktion simuliert erst das Lindenmayer-System, um die Größe zu erhalten.
# Danach wird das Lindenmayer-System gezeichnet und auf die agegebene Größe skalliert # Danach wird das Lindenmayer-System gezeichnet und auf die angegebene Größe skaliert
def draw(lSystem, recursionDepth, middle, rotation, size, color): def draw(lSystem, recursionDepth, middle, rotation, size, color):
# Informationen zum Zeichnen des Lindenmayer-System # Informationen zum Zeichnen des Lindenmayer-Systems
drawingInfo = DrawingInfo(lSystem, turtle.Vec2D(0, 0), rotation, color, 1, recursionDepth) drawingInfo = DrawingInfo(lSystem, turtle.Vec2D(0, 0), rotation, color, 1, recursionDepth)
turtleObject = newTurtle() # erstellt eine neue turtle turtleObject = newTurtle() # erstellt eine neue turtle
drawing = Drawing(drawingInfo, turtleObject) drawing = Drawing(drawingInfo, turtleObject)
@ -141,10 +141,10 @@ def draw(lSystem, recursionDepth, middle, rotation, size, color):
xScale = size[0] / distance[0] # Skallierungswert für die Höhe xScale = size[0] / distance[0] # Skallierungswert für die Höhe
yScale = size[1] / distance[1] # Skallierungswert für die Breite yScale = size[1] / distance[1] # Skallierungswert für die Breite
# Der kleinere Skallierungswert wird benutzt damit die Zeichnung noch innerhalb des Bereichs ist. # Der kleinere Skalierungswert wird benutzt damit die Zeichnung noch innerhalb des Bereichs ist.
scale = yScale if xScale > yScale else xScale scale = yScale if xScale > yScale else xScale
# Die Sartposition für die Zeichnung wird berechnet indem die Startpostion berechnet wird, # Die Startposition für die Zeichnung wird berechnet, indem die Startpostion berechnet wird,
# wenn der Mittelpunkt bei (0, 0) liegen soll. Dieser Punkt wird dann um den gewählten Mittelpunkt verschoben. # wenn der Mittelpunkt bei (0, 0) liegen soll. Dieser Punkt wird dann um den gewählten Mittelpunkt verschoben.
pos = middle + (-minVec - distance * (1/2)) * scale pos = middle + (-minVec - distance * (1/2)) * scale

View File

@ -17,14 +17,14 @@ class Save:
loadedFilepath = None loadedFilepath = None
backgroundColor = "white" # aktuelle Hintergrundfarbe des Fensters backgroundColor = "white" # aktuelle Hintergrundfarbe des Fensters
# Funktion, um eine Information über den Standartwert einer Benutzereingabe anzugeben # Funktion, um eine Information über den Standardwert einer Benutzereingabe anzugeben
def defaultValueMsg(defaultValue): def defaultValueMsg(defaultValue):
if defaultValue == None: if defaultValue == None:
return "" return ""
return f" (Standartwert: {defaultValue})" return f" (Standartwert: {defaultValue})"
# liest eine Nutzereingabe mit der entsprechenden Beschreibung ein und infomiert den # liest eine Nutzereingabe mit der entsprechenden Beschreibung ein und informiert den
# Nutzer über den Standartwert # Nutzer über den Standardwert
def inputWithDefault(description, defaultValue): def inputWithDefault(description, defaultValue):
return input(f"{description}{defaultValueMsg(defaultValue)}: ") return input(f"{description}{defaultValueMsg(defaultValue)}: ")
@ -40,7 +40,7 @@ def inputNum(numberType, description, rangeErrorMsg, minRange, maxRange, default
return number # wird diese zurückgegeben. return number # wird diese zurückgegeben.
print(rangeErrorMsg) # Ansonsten wird eine Fehlermeldung ausgegeben. print(rangeErrorMsg) # Ansonsten wird eine Fehlermeldung ausgegeben.
except ValueError: except ValueError:
# Wurde nichts eingegeben und es gibt einen Standartwert, wird dieser zurückgegeben. # Wurde nichts eingegeben und es gibt einen Standardwert, wird dieser zurückgegeben.
if inputValue == "" and defaultValue != None: if inputValue == "" and defaultValue != None:
return defaultValue return defaultValue
print("Fehler: keine gültige Zahl") print("Fehler: keine gültige Zahl")
@ -56,7 +56,7 @@ def inputColor(turtleObject, question, defaultValue = None):
if defaultValue != None: if defaultValue != None:
return defaultValue # wird der Standartwert verwendet return defaultValue # wird der Standartwert verwendet
inputColorError() # bzw. ein Fehler ausgegeben und die Abfrage wiederholt, inputColorError() # bzw. ein Fehler ausgegeben und die Abfrage wiederholt,
# wenn es keinen Standartwert gibt # wenn es keinen Standardwert gibt
else: else:
try: try:
# Die Eingabe wird in eine RGB-Tuple umgewandelt. # Die Eingabe wird in eine RGB-Tuple umgewandelt.
@ -64,7 +64,7 @@ def inputColor(turtleObject, question, defaultValue = None):
turtleObject.pencolor(color) # der RGB-Wert wird getestet turtleObject.pencolor(color) # der RGB-Wert wird getestet
return color return color
# hat der Benutzer keinen gültigen RGB-Wert eingeben, wird ein Fehler geworfen und es # hat der Benutzer keinen gültigen RGB-Wert eingeben, wird ein Fehler geworfen und es
# wird probiert ob ein Tk-Farbname verwendet wurde. # wird probiert, ob ein Tk-Farbname verwendet wurde.
except: except:
try: try:
turtleObject.pencolor(inputValue) turtleObject.pencolor(inputValue)
@ -131,7 +131,7 @@ e: als Bilddatei exportieren""")
def afterClick(vec): def afterClick(vec):
pos.append(vec) pos.append(vec)
if len(pos) == 2: # Wenn zwei Punkte angeklickt wurden if len(pos) == 2: # Wenn zwei Punkte angeklickt wurden
# Nach einem Klick wird die Funktion nicht mehr aufgerufen # Nach einem Klick wird die Funktion nicht mehr aufgerufen.
turtleObject.screen.onclick(None) turtleObject.screen.onclick(None)
from0to1 = pos[1] - pos[0] # Vektor von Punkt Index 0 zu Punkt Index 1 from0to1 = pos[1] - pos[0] # Vektor von Punkt Index 0 zu Punkt Index 1
middle = pos[0] + (1/2) * from0to1 # Mittelpunkt der beiden angeklickten Punkte middle = pos[0] + (1/2) * from0to1 # Mittelpunkt der beiden angeklickten Punkte
@ -139,7 +139,7 @@ e: als Bilddatei exportieren""")
drawer.draw(lSystem, recursionDepth, middle, rotation, size, color) # zeichnet das Lindenmayer-System drawer.draw(lSystem, recursionDepth, middle, rotation, size, color) # zeichnet das Lindenmayer-System
takeInput(turtleObject) # ruft sich rekursiv auf, um den nächsten Befehl entgegen zu nehmen takeInput(turtleObject) # ruft sich rekursiv auf, um den nächsten Befehl entgegen zu nehmen
# registriert die afterClick-Funktion als event # registriert die afterClick-Funktion als Event
turtleObject.screen.onclick(lambda x, y: afterClick(turtle.Vec2D(x, y))) turtleObject.screen.onclick(lambda x, y: afterClick(turtle.Vec2D(x, y)))
case _: case _:
print("Bitte j oder n eingeben") print("Bitte j oder n eingeben")
@ -160,7 +160,7 @@ e: als Bilddatei exportieren""")
drawer.delete(iDelete) # löscht das Lindenmayer-System drawer.delete(iDelete) # löscht das Lindenmayer-System
case "s": case "s":
# fragt nach dem Dateipfad. Wenn bereits eine Datei geladen oder gespeichert wurde, # fragt nach dem Dateipfad. Wenn bereits eine Datei geladen oder gespeichert wurde,
# wird der dazugehörige Pfad als Standartwert verwendet. # wird der dazugehörige Pfad als Standardwert verwendet.
filepath = inputString("Dateipfad zum Speichern eingeben", loadedFilepath) filepath = inputString("Dateipfad zum Speichern eingeben", loadedFilepath)
loadedFilepath = filepath loadedFilepath = filepath