Compare commits

...

7 Commits

Author SHA1 Message Date
bdb11c22d0 doc 2022-01-23 10:10:59 +01:00
7d4884946f comments 2022-01-13 22:12:01 +01:00
42cbbe788e l-System spezific default recursion depth 2022-01-08 20:55:17 +01:00
743c0306da export as image 2022-01-06 21:40:07 +01:00
5087df5bdd save and load 2022-01-05 23:15:12 +01:00
b509527b7c delete drawings 2022-01-05 18:16:15 +01:00
30caffb577 custom postion drawing 2022-01-02 21:39:17 +01:00
7 changed files with 485 additions and 76 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

@ -1,25 +1,27 @@
import drawer
import turtle
import lSystems
import inputHelper
turtle.tracer(0, 0)
turtleObject = turtle.Turtle()
turtleObject.hideturtle()
for i, lSystem in enumerate(lSystems.LSystems):
print(f"{i}: {lSystem.name}")
lSystemIndex = inputHelper.inputNum(int, "Bitte ein Lindenmayer-System auswählen und die entsprechende Nummer eingeben: ", "Es gibt kein L-System mit dieser Nummer.", 0, len(lSystems.LSystems) - 1)
lSystem = lSystems.LSystems[lSystemIndex]
simulatedDraw = drawer.DrawerSimulation(lSystem.productionRules, lSystem.angel, 1, turtle.Vec2D(0, 0), 90.0, turtleObject)
simulatedDraw.draw(lSystem.startWord, 5)
maxVec = simulatedDraw.edges.maxVec()
minVec = simulatedDraw.edges.minVec()
distance = maxVec - minVec
xScale = turtleObject.screen.window_width() / distance[0]
yScale = turtleObject.screen.window_height() / distance[1]
scale = yScale if xScale > yScale else xScale
pos = (-minVec - distance * (1/2)) * scale
print(pos)
print(distance)
actualDrawer = drawer.Drawer(lSystem.productionRules, lSystem.angel, scale, pos, 90.0, turtleObject)
actualDrawer.draw(lSystem.startWord, 5)
turtleObject.screen.exitonclick()
import turtle
import drawer
import sys
def quit():
print() # Letzte Zeile wird abgeschlossen
sys.exit()
# Dummy-Turtle, die erzeugt wird damit sich das Fenster öffnet und getestet werden kann,
# ob der Nutzer eine korrekte Farbeingabe gemacht hat. Allerdings wird mit dieser Turtle nichts gezeichnet.
turtleObject = drawer.newTurtle()
screen = turtleObject.screen
screen.colormode(255) # ändert den Farbmodus auf RGB mit 24 Bits
# Programm wird geschlossen, wenn das Fenster geschlossen wird
# http://ostack.cn/?qa=1086384/how-to-detect-x-close-button-in-python-turtle-graphics
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 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 Ausgabeoperationen werden bereits vorher abgefangen.
except EOFError:
quit()
screen.mainloop() # führt den Tk-mainloop aus

View File

@ -1,67 +1,174 @@
import turtle
from dataclasses import dataclass
import lSystems
# um die Rotation und Position einer turtle zu speichern
@dataclass
class State:
heading: float
position: turtle.Vec2D
class Drawer():
def __init__(self, productionRules, angel, forwardDistance, startPosition, startRotation, turtle):
self.productionRules = productionRules
self.angel = angel
self.forwardDistance = forwardDistance
self.turtle = turtle
self.turtle.pendown()
self.turtle.setposition(startPosition)
self.turtle.setheading(startRotation)
# enthält alle Information, die notwendig sind, um ein Lindenmayer-System zu zeichnen
@dataclass
class DrawingInfo:
lSystem: lSystems.LSytem
position: turtle.Vec2D
rotation: float
color: any
scale: float
recursionDepth: int
# enthält zusätzlich zu DrawingInfo die turtle mit der die Zeichnung gemacht wurde
@dataclass
class Drawing:
info: DrawingInfo
turtle: turtle.Turtle
drawings = [] # speichert alle Zeichnungen (Objekte der Klasse Drawing), die anzeigt werden
class Drawer():
def __init__(self, drawing):
# verschiedensten Variablen werden Werte zugewiesen
self.startWord = drawing.info.lSystem.startWord
self.recursionDepth = drawing.info.recursionDepth
self.productionRules = drawing.info.lSystem.productionRules
self.angel = drawing.info.lSystem.angel
self.forwardDistance = drawing.info.scale
self.turtle = drawing.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()
self.turtle.setposition(drawing.info.position)
self.turtle.setheading(drawing.info.rotation)
self.pendown()
self.turtle.pencolor(drawing.info.color) # ändert die Farbe
# Diese Methode wird nur bei der Klasse DrawerSimulation gebraucht, um die Eckpunkte zu speichern.
def storeEdges(self):
pass
def draw(self, word, n):
# 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) Rekursionstiefe an.
# Beim ersten Aufrufen wird die Methode ohne Parameter aufgerufen.
def draw(self, word = None, n = None):
# weist die Standardwerte zu
if n == None:
n = self.recursionDepth
if word == None:
word = self.startWord
turtleStates = []
if n == 0:
self.turtle.screen.update()
if n == 0: # zeichnen beendet
self.turtle.screen.update() # updated den Bildschirm, sodass das Lindenmayer-System anzeigt wird
return
for character in word:
for character in word: # itteriert über das Wort
match character:
case "F":
self.turtle.forward(self.forwardDistance)
self.storeEdges()
self.turtle.forward(self.forwardDistance) # bewegt die turtle nach vorne
self.storeEdges() # speichert die Eckpunkte (für die Klasse DrawerSimulation)
case "+":
self.turtle.left(self.angel)
self.turtle.left(self.angel) # dreht die turtle nach links
case "-":
self.turtle.right(self.angel)
self.turtle.right(self.angel) # dreht die turtle nach rechts
case "[":
# speichert Position und Rotation der turtle
turtleStates.append(State(self.turtle.heading(), self.turtle.position()))
case "]":
# Die letzte abgespeicherte Position und Rotation wird wiederhergestellt
self.turtle.penup() # um einen Strich zu der neuen Position zu vermeiden
state = turtleStates.pop()
self.turtle.setposition(state.position)
self.turtle.setheading(state.heading)
self.pendown()
# Bei einem Nicht-Terminal werden die jeweiligen Produktionsregeln ausgeführt
if character in self.productionRules:
self.draw(self.productionRules[character], n - 1)
# Klasse für das Speichern der Eckpunkte
class Edges:
def __init__(self):
# Die Klasse DrawerSimulation wird immer mit der Startpostion (0, 0) aufgerufen.
# Deshalb sind die minimalen und maximalen Werte zu anfangs auch alle 0.
self.min = [0, 0]
self.max = [0, 0]
# Methoden, die die beiden Eckpunkte als Vektor zurückgeben
def minVec(self):
return turtle.Vec2D(self.min[0], self.min[1])
def maxVec(self):
return turtle.Vec2D(self.max[0], self.max[1])
class DrawerSimulation(Drawer):
def __init__(self, productionRules, angel, forwardDistance, startPosition, startRotation, turtle):
super(DrawerSimulation, self).__init__(productionRules, angel, forwardDistance, startPosition, startRotation, turtle)
self.turtle.penup()
def __init__(self, drawing):
super(DrawerSimulation, self).__init__(drawing) # Konstruktor der Superklasse
self.edges = Edges()
# ändert die Koordinaten der Eckpunkte, wenn die turtle über die bisherigen hinaus geht.
def storeEdges(self):
for i, koord in enumerate(self.turtle.position()):
if koord < self.edges.min[i]:
self.edges.min[i] = koord
elif koord > self.edges.max[i]:
self.edges.max[i] = koord
# deaktiviert das Zeichnen
def pendown(self):
pass
# Die Funktion simuliert erst das Lindenmayer-System, um die Größe zu erhalten.
# 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-Systems
drawingInfo = DrawingInfo(lSystem, turtle.Vec2D(0, 0), rotation, color, 1, recursionDepth)
turtleObject = newTurtle() # erstellt eine neue turtle
drawing = Drawing(drawingInfo, turtleObject)
simulatedDraw = DrawerSimulation(drawing)
simulatedDraw.draw() # simuliert das Lindenmayer-System
# die beiden diagonalen Eckpunkte der Fläche in der die Zeichnung liegen würde
maxVec = simulatedDraw.edges.maxVec()
minVec = simulatedDraw.edges.minVec()
distance = maxVec - minVec
xScale = size[0] / distance[0] # Skallierungswert für die Höhe
yScale = size[1] / distance[1] # Skallierungswert für die Breite
# Der kleinere Skalierungswert wird benutzt damit die Zeichnung noch innerhalb des Bereichs ist.
scale = yScale if xScale > yScale else xScale
# 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
# zeichnet das Lindenmayer-System mit den berechneten Werten
drawing.info.position = pos
drawing.info.scale = scale
drawScaled(drawing)
# erstellt eine neue turtle und nimmt einige Einstellungen vor.
def newTurtle():
turtleObject = turtle.Turtle()
turtleObject.hideturtle() # turtle wird nicht angezeigt
# macht das Zeichnen so schnell wie möglich und für das Sehen der Änderungen ist ein Update des Bildschirms erforderlich
turtleObject._tracer(0, 0)
return turtleObject
# löscht eine Zeichnung
def delete(i):
drawings[i].turtle.clear()
del drawings[i]
# zeichnet ein Lindenmayer-System und es der Liste hinzu
def drawScaled(drawing):
actualDrawer = Drawer(drawing)
actualDrawer.draw()
drawings.append(drawing)

View File

@ -1,13 +0,0 @@
def inputInt(description, indexErrorMsg):
lSystemIndex: int
while inputError:
try:
lSystemIndex = int(input(description))
except ValueError:
print("Fehler: keine gültige Zahl")
continue
inputError = lSystemIndex < 0 or lSystemIndex >= len(lSystems.LSystems)
if inputError:
print(indexErrorMsg)
return lSystemIndex

View File

@ -1,17 +1,220 @@
def inputNum(inputType, description, rangeErrorMsg, minRange, maxRange, defaultValue = None):
n: int
inputError = True
while inputError:
inputValue = input(description)
import drawer
import turtle
import lSystems
import pickle
from dataclasses import dataclass
from PIL import Image
import io
import sys
# Klasse mit allen nötigen Informationen, um den Zustand des Programms abzuspeichern.
@dataclass
class Save:
backgroundColor: any
drawingInfos: [drawer.DrawingInfo]
# Pfad der geladenen Datei, um eine Option anzubieten schnell unter der gleichen Datei zu speichern.
loadedFilepath = None
backgroundColor = "white" # aktuelle Hintergrundfarbe des Fensters
# 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 informiert den
# Nutzer über den Standardwert
def inputWithDefault(description, defaultValue):
return input(f"{description}{defaultValueMsg(defaultValue)}: ")
# Hilfsfunktion für die Nutzereingabe einer Zahl in einem bestimmten Bereich
def inputNum(numberType, description, rangeErrorMsg, minRange, maxRange, defaultValue = None):
while True: # Die Eingabeauffordung wird wiederholt, wenn die Eingabe ungültig ist.
inputValue = inputWithDefault(description, defaultValue) # Eingabe wird eingelesen
try:
n = inputType(inputValue)
# Der Eingabewert Wert wird zu einer Zahl konvertiert (float oder int).
# Wurde keine Zahl eingegeben, wird ein ValueError ausgelöst.
number = numberType(inputValue)
if number >= minRange and number <= maxRange: # liegt die Zahl im entsprechenden Bereich,
return number # wird diese zurückgegeben.
print(rangeErrorMsg) # Ansonsten wird eine Fehlermeldung ausgegeben.
except ValueError:
# 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")
continue
inputError = n < minRange or n > maxRange
if inputError:
print(rangeErrorMsg)
return n
def inputColorError():
print("Fehler: ungültige Farbeingabe")
# fragt eine TK-Farbe ab
def inputColor(turtleObject, question, defaultValue = None):
while True: # Die Eingabeauffordung wird wiederholt, wenn die Eingabe ungültig ist.
inputValue = input(f"{question} RGB-Wert eingeben, wobei die Farben mit Leerzeichen getrennt werden, oder einen Tk-Farbnamen eingeben [0 0 0 - 255 255 255 oder Farben auf https://www.tcl.tk/man/tcl8.4/TkCmd/colors.html]{defaultValueMsg(defaultValue)}: ")
if inputValue == "": # Wenn keine Farbe eingeben wurde,
if defaultValue != None:
return defaultValue # wird der Standartwert verwendet
inputColorError() # bzw. ein Fehler ausgegeben und die Abfrage wiederholt,
# wenn es keinen Standardwert gibt
else:
try:
# Die Eingabe wird in eine RGB-Tuple umgewandelt.
color = tuple([int(color) for color in inputValue.split()])
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.
except:
try:
turtleObject.pencolor(inputValue)
return inputValue
except:
# Wurde kein Tk-Farbname verwendet, ist die Eingabe ungültig und
# der Nutzer wird erneut nach einer Farbe gefragt
inputColorError()
# fordert den Nutzer auf einen String einzugeben
def inputString(question, defaultValue = None):
while True: # Die Eingabeauffordung wird wiederholt, wenn die Eingabe ungültig ist.
inputValue = inputWithDefault(question, defaultValue)
if inputValue != "":
return inputValue
if defaultValue != None: # wurde nichts eingegeben, wird der Standartwert verwendet
return defaultValue
# Die Funktion führt einen Befehl aus und ruft sich daraufhin rekursiv auf.
def takeInput(turtleObject):
global backgroundColor
global loadedFilepath
inputValue = input("Bitte einen Befehl eingeben. h für Hilfe: ")
match inputValue:
case "h":
# Hilfeseite
print("""h: Hilfe anzeigen
d: ein neues Lindenmayer-System zeichnen
l: ein Lindenmayer-System löschen
b: Hintergrundfarbe ändern
q: Programm beenden
s: Lindenmayer-Systeme speichern
r: zuvor gespeicherte Lindenmayer-Systeme wiederherstellen
e: als Bilddatei exportieren""")
case "d":
# gibt vorhandene Lindenmayer-Systeme aus
for i, lSystem in enumerate(lSystems.LSystems):
print(f"{i}: {lSystem.name}")
# verschiedene Nutzereingaben
lSystemIndex = inputNum(int, "Bitte ein Lindenmayer-System auswählen und die entsprechende Nummer eingeben", "Es gibt kein L-System mit dieser Nummer.", 0, len(lSystems.LSystems) - 1)
lSystem = lSystems.LSystems[lSystemIndex]
recursionDepth = inputNum(int, "Rekursiontiefe des Lindenmayer-Systems eingeben [1-50]", "Rekursionstiefe nicht im vorgegebenen Bereich.", 1, 50, lSystem.recursionDepth)
rotation = inputNum(float, "Bitte die Rotation in Grad gegen den Uhrzeigersinn angeben, wobei 0° rechts ist", "nur Gradzahlen von 0 bis 360 werden akzeptiert.", 0, 360, 90)
color = inputColor(turtleObject, "Welche Farbe soll das Lindenmayer-System haben?", "black")
inputError = True
while inputError:
match input("Möchtest du, dass das Lindenmayer-System das ganze Fenster ausfüllt? [J/n]: ").lower():
case "j"|"": # Ja ist die Standartantwort.
inputError = False
# zeichnet das Lindenmayer-System
# Von der Größe des Fensters werden drei Pixel abgezogen, da die Linien auch
# eine Breite haben und sonst die Linien am Rand nicht zu sehen sind.
drawer.draw(lSystem, recursionDepth, turtle.Vec2D(0, 0), rotation, turtle.Vec2D(turtleObject.screen.window_width(), turtleObject.screen.window_height()) - turtle.Vec2D(3, 3), color)
takeInput(turtleObject) # ruft sich rekursiv auf, um den nächsten Befehl entgegen zu nehmen
case "n":
inputError = False
print("Klicke bitte die beiden diagonalen Eckpunkte der Fläche an in der das Lindenmayer-System gezeichnet werden soll.")
pos = [] # Liste, in der die angeklickte Punkte gespeichert werden
# Die Funktion wird nach einem Klick mit einem Ortsvektor des angeklickten Punktes aufgerufen.
def afterClick(vec):
pos.append(vec)
if len(pos) == 2: # Wenn zwei Punkte angeklickt wurden
# 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
size = turtle.Vec2D(abs(from0to1[0]), abs(from0to1[1]))
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
turtleObject.screen.onclick(lambda x, y: afterClick(turtle.Vec2D(x, y)))
case _:
print("Bitte j oder n eingeben")
case "b":
# fragt nach einer Hintergrundfarbe und ändert diese entsprechend.
backgroundColor = inputColor(turtleObject, "Hintergrundfarbe eingeben")
turtleObject.screen.bgcolor(backgroundColor)
case "q":
sys.exit() # beendet das Programm
case "l":
if len(drawer.drawings) == 0:
print("Es gibt nichts, was man löschen könnte.")
else:
# gibt die angezeigten Lindenmayer-Systeme aus
for i, drawing in enumerate(drawer.drawings):
print(f"{i}: {drawing.info.lSystem.name} Position: {drawing.info.position}")
iDelete = inputNum(int, "Nummer des zu löschenden Zeichnung eingeben", "Es gibt keine Zeichnung mit dieser Nummer", 0, len(drawer.drawings) - 1)
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 Standardwert verwendet.
filepath = inputString("Dateipfad zum Speichern eingeben", loadedFilepath)
loadedFilepath = filepath
# Beim Speichern der angezeigten Lindenmayer-Systeme werden die turtles nicht benötigt.
drawingInfos = [drawing.info for drawing in drawer.drawings]
save = Save(backgroundColor, drawingInfos) # Objekt mit allen Daten, die gespeichert werden sollen.
try:
file = open(filepath, "wb")
pickle.dump(save, file) # schreibt die Daten in die Datei
file.close()
# infomiert den Nutzer, dass seine Daten gespeichert wurden
print("erfolgreich gespeichert")
except Exception as err:
print(err) # gibt mögliche IO-Fehler aus
case "r":
filepath = inputString("Datei, die geladen werden soll eingeben")
loadedFilepath = filepath
try:
file = open(filepath, "rb")
save = pickle.load(file) # liest die Datei
file.close()
turtleObject.screen.clear() # leert das Fenster
# ändert die Hintergrundfarbe
backgroundColor = save.backgroundColor
turtleObject.screen.bgcolor(save.backgroundColor)
# zeichnet die Lindenmayer-Systeme
for drawingInfo in save.drawingInfos:
drawing = drawer.Drawing(drawingInfo, drawer.newTurtle())
drawer.drawScaled(drawing)
except Exception as err:
print(err) # gibt mögliche IO-Fehler aus
case "e":
# speichert das Bild. für genauere Informationen siehe:
# https://stackoverflow.com/questions/34777676/how-to-convert-a-python-tkinter-canvas-postscript-file-to-an-image-file-readable
canvas = turtleObject.screen.getcanvas()
postscript = canvas.postscript(colormode = "color") # exportiert das Bild im postscript Format
image = Image.open(io.BytesIO(postscript.encode("utf-8"))) # lädt das postscript mit PIL
# fragt den Nutzer nach einem Dateipfad
filepath = inputString("Dateipfad für das zu exportierende Bild eingeben. Das Bildformat wird über die Dateiendung bestimmt")
try:
image.save(filepath) # speichert das Bild
print("Bild gespeichert")
except ValueError: # PIL löst einen ValueError aus, wenn die Dateiendung unbekannt ist
print("Fehler: unbekannte Dateiendung")
except Exception as err:
print(err) # gibt andere Fehler aus
case _:
print("unbekannter Befehl")
# Bei dem d(raw)-Befehl wurde die Funktion bereits rekursiv aufgerufen.
# Ansonsten wird sie noch aufgerufen und der nächste Befehl kann ausgeführt werden.
if inputValue != "d":
takeInput(turtleObject)

View File

@ -1,18 +1,26 @@
from dataclasses import dataclass
import typing
# beschreibt ein Lindenmayer-System
@dataclass
class LSytem:
name: str
startWord: str
productionRules: typing.Dict[str, str]
angel: float
recursionDepth: int
# dem Programm bekannte Lindenmayer-Systeme
LSystems = [
LSytem("AB a", "F", {"F": "F[+F]F[-F]F"}, 25.7),
LSytem("AB b", "F", {"F": "F[+F]F[-F][F]"}, 20.0),
LSytem("AB c", "F", {"F": "FF-[-F+F+F]+[+F-F-F]"}, 22.5),
LSytem("AB d", "X", {"X": "F[+X]F[-X]+X", "F": "FF"}, 20.0),
LSytem("AB e", "X", {"X": "F[+X][-X]FX", "F": "FF"}, 25.7),
LSytem("AB f", "X", {"X": "F-[[X]+X]+F[+FX]-X", "F": "FF"}, 22.5),
# Lindenmayer-Systeme vom AB
LSytem("toter Busch", "F", {"F": "F[+F]F[-F]F"}, 25.7, 5),
LSytem("Gretenbaum", "F", {"F": "F[+F]F[-F][F]"}, 20.0, 5),
LSytem("Laubbaum", "F", {"F": "FF-[-F+F+F]+[+F-F-F]"}, 22.5, 4),
LSytem("dürrer Strauch", "X", {"X": "F[+X]F[-X]+X", "F": "FF"}, 20.0, 7),
LSytem("sysmetrisches Pflänzchen", "X", {"X": "F[+X][-X]FX", "F": "FF"}, 25.7, 7),
LSytem("schiefer Strauch", "X", {"X": "F-[[X]+X]+F[+FX]-X", "F": "FF"}, 22.5, 5),
# bekannte Lindenmayer-Systeme
LSytem("Drachenkurve", "FX", {"X": "X+YF+", "Y": "-FX-Y"}, 90.0, 15),
LSytem("Kochsche Schneeflocke", "F--F--F", {"F": "F+F--F+F"}, 60.0, 5),
]

10
setup.py Normal file
View File

@ -0,0 +1,10 @@
from setuptools import setup, find_packages
setup(
name='lindenmayer',
version='0.1.0',
packages=find_packages(include=['lindenmayer']),
install_requires=[
"pillow",
],
)