From bdb11c22d037a83d91993606e3961551d12b45f1 Mon Sep 17 00:00:00 2001 From: MrGeorgen Date: Sun, 23 Jan 2022 10:10:59 +0100 Subject: [PATCH] doc --- README.md | 94 +++++++++++++++++++++++++++++++++++++- lindenmayer/__main__.py | 4 +- lindenmayer/drawer.py | 18 ++++---- lindenmayer/inputHelper.py | 18 ++++---- 4 files changed, 113 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 767ea49..df72365 100644 --- a/README.md +++ b/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 diff --git a/lindenmayer/__main__.py b/lindenmayer/__main__.py index bfeee71..55f1789 100644 --- a/lindenmayer/__main__.py +++ b/lindenmayer/__main__.py @@ -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 diff --git a/lindenmayer/drawer.py b/lindenmayer/drawer.py index 087e535..00745f1 100644 --- a/lindenmayer/drawer.py +++ b/lindenmayer/drawer.py @@ -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 diff --git a/lindenmayer/inputHelper.py b/lindenmayer/inputHelper.py index a9db68c..a408367 100644 --- a/lindenmayer/inputHelper.py +++ b/lindenmayer/inputHelper.py @@ -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