doc
This commit is contained in:
94
README.md
94
README.md
@ -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
|
||||
|
||||
@ -17,11 +17,11 @@ screen.colormode(255) # ändert den Farbmodus auf RGB mit 24 Bits
|
||||
screen.getcanvas().winfo_toplevel().protocol("WM_DELETE_WINDOW", quit)
|
||||
try:
|
||||
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.
|
||||
# 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
|
||||
# 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:
|
||||
quit()
|
||||
screen.mainloop() # führt den Tk-mainloop aus
|
||||
|
||||
@ -36,7 +36,7 @@ class Drawer():
|
||||
self.forwardDistance = drawing.info.scale
|
||||
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
|
||||
# zur neuen Position keine Linie zeichnet.
|
||||
self.turtle.penup()
|
||||
@ -50,16 +50,16 @@ class Drawer():
|
||||
def storeEdges(self):
|
||||
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.
|
||||
def pendown(self):
|
||||
self.turtle.pendown()
|
||||
|
||||
# Die Methode zeichnet das Lindenmayer-System.
|
||||
# n gibt die (verbleibende) Rekursiontiefe an.
|
||||
# Beim ersten Aufrufen wird die Methode ohne Paramenter aufgerufen.
|
||||
# n gibt die (verbleibende) Rekursionstiefe an.
|
||||
# Beim ersten Aufrufen wird die Methode ohne Parameter aufgerufen.
|
||||
def draw(self, word = None, n = None):
|
||||
# weist die Standartwerte zu
|
||||
# weist die Standardwerte zu
|
||||
if n == None:
|
||||
n = self.recursionDepth
|
||||
if word == None:
|
||||
@ -124,9 +124,9 @@ class DrawerSimulation(Drawer):
|
||||
pass
|
||||
|
||||
# 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):
|
||||
# Informationen zum Zeichnen des Lindenmayer-System
|
||||
# Informationen zum Zeichnen des Lindenmayer-Systems
|
||||
drawingInfo = DrawingInfo(lSystem, turtle.Vec2D(0, 0), rotation, color, 1, recursionDepth)
|
||||
turtleObject = newTurtle() # erstellt eine neue turtle
|
||||
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
|
||||
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
|
||||
|
||||
# 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.
|
||||
pos = middle + (-minVec - distance * (1/2)) * scale
|
||||
|
||||
|
||||
@ -17,14 +17,14 @@ class Save:
|
||||
loadedFilepath = None
|
||||
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):
|
||||
if defaultValue == None:
|
||||
return ""
|
||||
return f" (Standartwert: {defaultValue})"
|
||||
|
||||
# liest eine Nutzereingabe mit der entsprechenden Beschreibung ein und infomiert den
|
||||
# Nutzer über den Standartwert
|
||||
# liest eine Nutzereingabe mit der entsprechenden Beschreibung ein und informiert den
|
||||
# Nutzer über den Standardwert
|
||||
def inputWithDefault(description, 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.
|
||||
print(rangeErrorMsg) # Ansonsten wird eine Fehlermeldung ausgegeben.
|
||||
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:
|
||||
return defaultValue
|
||||
print("Fehler: keine gültige Zahl")
|
||||
@ -56,7 +56,7 @@ def inputColor(turtleObject, question, defaultValue = None):
|
||||
if defaultValue != None:
|
||||
return defaultValue # wird der Standartwert verwendet
|
||||
inputColorError() # bzw. ein Fehler ausgegeben und die Abfrage wiederholt,
|
||||
# wenn es keinen Standartwert gibt
|
||||
# wenn es keinen Standardwert gibt
|
||||
else:
|
||||
try:
|
||||
# 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
|
||||
return color
|
||||
# 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:
|
||||
try:
|
||||
turtleObject.pencolor(inputValue)
|
||||
@ -131,7 +131,7 @@ e: als Bilddatei exportieren""")
|
||||
def afterClick(vec):
|
||||
pos.append(vec)
|
||||
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)
|
||||
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
|
||||
@ -139,7 +139,7 @@ e: als Bilddatei exportieren""")
|
||||
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
|
||||
|
||||
# registriert die afterClick-Funktion als event
|
||||
# registriert die afterClick-Funktion als Event
|
||||
turtleObject.screen.onclick(lambda x, y: afterClick(turtle.Vec2D(x, y)))
|
||||
case _:
|
||||
print("Bitte j oder n eingeben")
|
||||
@ -160,7 +160,7 @@ e: als Bilddatei exportieren""")
|
||||
drawer.delete(iDelete) # löscht das Lindenmayer-System
|
||||
case "s":
|
||||
# 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)
|
||||
loadedFilepath = filepath
|
||||
|
||||
|
||||
Reference in New Issue
Block a user