This commit is contained in:
2022-01-13 22:12:01 +01:00
parent 42cbbe788e
commit 7d4884946f
4 changed files with 183 additions and 67 deletions

View File

@ -1,12 +1,27 @@
import inputHelper import inputHelper
import turtle import turtle
import drawer 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() turtleObject = drawer.newTurtle()
screen = turtleObject.screen screen = turtleObject.screen
screen.colormode(255) 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: try:
inputHelper.takeInput(turtleObject) inputHelper.takeInput(turtleObject)
# Die Python integrierte "input"-Funktion wirft diesen Fehler, wenn der Nutzer die Tastenkombi 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.
except EOFError: except EOFError:
quit() quit()
screen.mainloop() screen.mainloop() # führt den Tk-mainloop aus

View File

@ -2,11 +2,13 @@ import turtle
from dataclasses import dataclass from dataclasses import dataclass
import lSystems import lSystems
# um die Rotation und Position einer turtle zu speichern
@dataclass @dataclass
class State: class State:
heading: float heading: float
position: turtle.Vec2D position: turtle.Vec2D
# enthält alle Information, die notwendig sind, um ein Lindenmayer-System zu zeichnen
@dataclass @dataclass
class DrawingInfo: class DrawingInfo:
lSystem: lSystems.LSytem lSystem: lSystems.LSytem
@ -16,78 +18,100 @@ class DrawingInfo:
scale: float scale: float
recursionDepth: int recursionDepth: int
# enthält zusätzlich zu DrawingInfo die turtle mit der die Zeichnung gemacht wurde
@dataclass @dataclass
class Drawing: class Drawing:
info: DrawingInfo info: DrawingInfo
turtle: turtle.Turtle turtle: turtle.Turtle
drawings = [] drawings = [] # speichert alle Zeichnungen (Objekte der Klasse Drawing), die anzeigt werden
class Drawer(): class Drawer():
def __init__(self, drawing): def __init__(self, drawing):
# verschiedensten Variablen werden Werte zugewiesen
self.startWord = drawing.info.lSystem.startWord self.startWord = drawing.info.lSystem.startWord
self.recursionDepth = drawing.info.recursionDepth self.recursionDepth = drawing.info.recursionDepth
self.productionRules = drawing.info.lSystem.productionRules self.productionRules = drawing.info.lSystem.productionRules
self.angel = drawing.info.lSystem.angel self.angel = drawing.info.lSystem.angel
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
# Zuvor wird das Zeichnen temporär deaktiviert, damit die turtle beim Bewegen
# zur neuen Position keine Linie zeichnet.
self.turtle.penup() self.turtle.penup()
self.turtle.setposition(drawing.info.position) self.turtle.setposition(drawing.info.position)
self.turtle.setheading(drawing.info.rotation) self.turtle.setheading(drawing.info.rotation)
self.pendown() self.pendown()
self.turtle.pencolor(drawing.info.color)
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): def storeEdges(self):
pass pass
# wrappt die pendown Methode der turtle, damit das zeichnen bei überschreiben der Methode
# deaktiviert werden kann.
def pendown(self): def pendown(self):
self.turtle.pendown() self.turtle.pendown()
# Die Methode zeichnet das Lindenmayer-System.
# n gibt die (verbleibende) Rekursiontiefe an.
# Beim ersten Aufrufen wird die Methode ohne Paramenter aufgerufen.
def draw(self, word = None, n = None): def draw(self, word = None, n = None):
# weist die Standartwerte zu
if n == None: if n == None:
n = self.recursionDepth n = self.recursionDepth
if word == None: if word == None:
word = self.startWord word = self.startWord
turtleStates = [] turtleStates = []
if n == 0: if n == 0: # zeichnen beendet
self.turtle.screen.update() self.turtle.screen.update() # updated den Bildschirm, sodass das Lindenmayer-System anzeigt wird
return return
for character in word: for character in word: # itteriert über das Wort
match character: match character:
case "F": case "F":
self.turtle.forward(self.forwardDistance) self.turtle.forward(self.forwardDistance) # bewegt die turtle nach vorne
self.storeEdges() self.storeEdges() # speichert die Eckpunkte (für die Klasse DrawerSimulation)
case "+": case "+":
self.turtle.left(self.angel) self.turtle.left(self.angel) # dreht die turtle nach links
case "-": case "-":
self.turtle.right(self.angel) self.turtle.right(self.angel) # dreht die turtle nach rechts
case "[": case "[":
# speichert Position und Rotation der turtle
turtleStates.append(State(self.turtle.heading(), self.turtle.position())) turtleStates.append(State(self.turtle.heading(), self.turtle.position()))
case "]": case "]":
self.turtle.penup() # Die letzte abgespeicherte Position und Rotation wird wiederhergestellt
self.turtle.penup() # um einen Strich zu der neuen Position zu vermeiden
state = turtleStates.pop() state = turtleStates.pop()
self.turtle.setposition(state.position) self.turtle.setposition(state.position)
self.turtle.setheading(state.heading) self.turtle.setheading(state.heading)
self.pendown() self.pendown()
# Bei einem Nicht-Terminal werden die jeweiligen Produktionsregeln ausgeführt
if character in self.productionRules: if character in self.productionRules:
self.draw(self.productionRules[character], n - 1) self.draw(self.productionRules[character], n - 1)
# Klasse für das Speichern der Eckpunkte
class Edges: class Edges:
def __init__(self): 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.min = [0, 0]
self.max = [0, 0] self.max = [0, 0]
# Methoden, die die beiden Eckpunkte als Vektor zurückgeben
def minVec(self): def minVec(self):
return turtle.Vec2D(self.min[0], self.min[1]) return turtle.Vec2D(self.min[0], self.min[1])
def maxVec(self): def maxVec(self):
return turtle.Vec2D(self.max[0], self.max[1]) return turtle.Vec2D(self.max[0], self.max[1])
class DrawerSimulation(Drawer): class DrawerSimulation(Drawer):
def __init__(self, drawing): def __init__(self, drawing):
super(DrawerSimulation, self).__init__(drawing) super(DrawerSimulation, self).__init__(drawing) # Konstruktor der Superklasse
self.edges = Edges() self.edges = Edges()
# ändert die Koordinaten der Eckpunkte, wenn die turtle über die bisherigen hinaus geht.
def storeEdges(self): def storeEdges(self):
for i, koord in enumerate(self.turtle.position()): for i, koord in enumerate(self.turtle.position()):
if koord < self.edges.min[i]: if koord < self.edges.min[i]:
@ -95,36 +119,55 @@ class DrawerSimulation(Drawer):
elif koord > self.edges.max[i]: elif koord > self.edges.max[i]:
self.edges.max[i] = koord self.edges.max[i] = koord
# deaktiviert das Zeichnen
def pendown(self): def pendown(self):
pass 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
def draw(lSystem, recursionDepth, middle, rotation, size, color): def draw(lSystem, recursionDepth, middle, rotation, size, color):
# Informationen zum Zeichnen des Lindenmayer-System
drawingInfo = DrawingInfo(lSystem, turtle.Vec2D(0, 0), rotation, color, 1, recursionDepth) drawingInfo = DrawingInfo(lSystem, turtle.Vec2D(0, 0), rotation, color, 1, recursionDepth)
turtleObject = newTurtle() turtleObject = newTurtle() # erstellt eine neue turtle
drawing = Drawing(drawingInfo, turtleObject) drawing = Drawing(drawingInfo, turtleObject)
simulatedDraw = DrawerSimulation(drawing) simulatedDraw = DrawerSimulation(drawing)
simulatedDraw.draw() simulatedDraw.draw() # simuliert das Lindenmayer-System
# die beiden diagonalen Eckpunkte der Fläche in der die Zeichnung liegen würde
maxVec = simulatedDraw.edges.maxVec() maxVec = simulatedDraw.edges.maxVec()
minVec = simulatedDraw.edges.minVec() minVec = simulatedDraw.edges.minVec()
distance = maxVec - minVec distance = maxVec - minVec
xScale = size[0] / distance[0] xScale = size[0] / distance[0] # Skallierungswert für die Höhe
yScale = size[1] / distance[1] yScale = size[1] / distance[1] # Skallierungswert für die Breite
# Der kleinere Skallierungswert 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,
# 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
# zeichnet das Lindenmayer-System mit den berechneten Werten
drawing.info.position = pos drawing.info.position = pos
drawing.info.scale = scale drawing.info.scale = scale
drawScaled(drawing) drawScaled(drawing)
# erstellt eine neue turtle und nimmt einige Einstellungen vor.
def newTurtle(): def newTurtle():
turtleObject = turtle.Turtle() turtleObject = turtle.Turtle()
turtleObject.hideturtle() 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) turtleObject._tracer(0, 0)
return turtleObject return turtleObject
# löscht eine Zeichnung
def delete(i): def delete(i):
drawings[i].turtle.clear() drawings[i].turtle.clear()
del drawings[i] del drawings[i]
# zeichnet ein Lindenmayer-System und es der Liste hinzu
def drawScaled(drawing): def drawScaled(drawing):
actualDrawer = Drawer(drawing) actualDrawer = Drawer(drawing)
actualDrawer.draw() actualDrawer.draw()

View File

@ -5,34 +5,42 @@ import pickle
from dataclasses import dataclass from dataclasses import dataclass
from PIL import Image from PIL import Image
import io import io
import sys
# Klasse mit allen nötigen Informationen, um den Zustand des Programms abzuspeichern.
@dataclass @dataclass
class Save: class Save:
backgroundColor: any backgroundColor: any
drawingInfos: [drawer.DrawingInfo] drawingInfos: [drawer.DrawingInfo]
# Pfad der geladenen Datei, um eine Option anzubieten schnell unter der gleichen Datei zu speichern.
loadedFilepath = None loadedFilepath = None
backgroundColor = "white" backgroundColor = "white" # aktuelle Hintergrundfarbe des Fensters
# Funktion, um eine Information über den Standartwert 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
# Nutzer über den Standartwert
def inputWithDefault(description, defaultValue): def inputWithDefault(description, defaultValue):
return input(f"{description}{defaultValueMsg(defaultValue)}: ") return input(f"{description}{defaultValueMsg(defaultValue)}: ")
def inputNum(inputType, description, rangeErrorMsg, minRange, maxRange, defaultValue = None): # Hilfsfunktion für die Nutzereingabe einer Zahl in einem bestimmten Bereich
while True: def inputNum(numberType, description, rangeErrorMsg, minRange, maxRange, defaultValue = None):
inputValue = inputWithDefault(description, defaultValue) while True: # Die Eingabeauffordung wird wiederholt, wenn die Eingabe ungültig ist.
inputValue = inputWithDefault(description, defaultValue) # Eingabe wird eingelesen
try: try:
number = inputType(inputValue) # Der Eingabewert Wert wird zu einer Zahl konvertiert (float oder int).
inputError = number < minRange or number > maxRange # Wurde keine Zahl eingegeben, wird ein ValueError ausgelöst.
if inputError: number = numberType(inputValue)
print(rangeErrorMsg) if number >= minRange and number <= maxRange: # liegt die Zahl im entsprechenden Bereich,
else: return number # wird diese zurückgegeben.
return number print(rangeErrorMsg) # Ansonsten wird eine Fehlermeldung ausgegeben.
except ValueError: except ValueError:
# Wurde nichts eingegeben und es gibt einen Standartwert, 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")
@ -40,38 +48,49 @@ def inputNum(inputType, description, rangeErrorMsg, minRange, maxRange, defaultV
def inputColorError(): def inputColorError():
print("Fehler: ungültige Farbeingabe") print("Fehler: ungültige Farbeingabe")
# fragt eine TK-Farbe ab
def inputColor(turtleObject, question, defaultValue = None): def inputColor(turtleObject, question, defaultValue = None):
while True: 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)}: ") 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 == "": if inputValue == "": # Wenn keine Farbe eingeben wurde,
if defaultValue != None: if defaultValue != None:
return defaultValue return defaultValue # wird der Standartwert verwendet
inputColorError() inputColorError() # bzw. ein Fehler ausgegeben und die Abfrage wiederholt,
# wenn es keinen Standartwert gibt
else: else:
try: try:
# Die Eingabe wird in eine RGB-Tuple umgewandelt.
color = tuple([int(color) for color in inputValue.split()]) color = tuple([int(color) for color in inputValue.split()])
turtleObject.pencolor(color) 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
# wird probiert ob ein Tk-Farbname verwendet wurde.
except: except:
try: try:
turtleObject.pencolor(inputValue) turtleObject.pencolor(inputValue)
return inputValue return inputValue
except: except:
# Wurde kein Tk-Farbname verwendet, ist die Eingabe ungültig und
# der Nutzer wird erneut nach einer Farbe gefragt
inputColorError() inputColorError()
# fordert den Nutzer auf einen String einzugeben
def inputString(question, defaultValue = None): def inputString(question, defaultValue = None):
while True: while True: # Die Eingabeauffordung wird wiederholt, wenn die Eingabe ungültig ist.
inputValue = inputWithDefault(question, defaultValue) inputValue = inputWithDefault(question, defaultValue)
if inputValue != "": if inputValue != "":
return inputValue return inputValue
if defaultValue != None: if defaultValue != None: # wurde nichts eingegeben, wird der Standartwert verwendet
return defaultValue return defaultValue
# Die Funktion führt einen Befehl aus und ruft sich daraufhin rekursiv auf.
def takeInput(turtleObject): def takeInput(turtleObject):
global backgroundColor global backgroundColor
global loadedFilepath
inputValue = input("Bitte einen Befehl eingeben. h für Hilfe: ") inputValue = input("Bitte einen Befehl eingeben. h für Hilfe: ")
match inputValue: match inputValue:
case "h": case "h":
# Hilfeseite
print("""h: Hilfe anzeigen print("""h: Hilfe anzeigen
d: ein neues Lindenmayer-System zeichnen d: ein neues Lindenmayer-System zeichnen
l: ein Lindenmayer-System löschen l: ein Lindenmayer-System löschen
@ -81,88 +100,121 @@ s: Lindenmayer-Systeme speichern
r: zuvor gespeicherte Lindenmayer-Systeme wiederherstellen r: zuvor gespeicherte Lindenmayer-Systeme wiederherstellen
e: als Bilddatei exportieren""") e: als Bilddatei exportieren""")
case "d": case "d":
# gibt vorhandene Lindenmayer-Systeme aus
for i, lSystem in enumerate(lSystems.LSystems): for i, lSystem in enumerate(lSystems.LSystems):
print(f"{i}: {lSystem.name}") print(f"{i}: {lSystem.name}")
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)
# 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] lSystem = lSystems.LSystems[lSystemIndex]
recursionDepth = inputNum(int, "Rekursiontiefe des Lindenmayer-Systems eingeben [1-50]", "Rekursionstiefe nicht im vorgegebenen Bereich.", 1, 50, lSystem.recursionDepth) 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) 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)
inputError = True
color = inputColor(turtleObject, "Welche Farbe soll das Lindenmayer-System haben?", "black") color = inputColor(turtleObject, "Welche Farbe soll das Lindenmayer-System haben?", "black")
inputError = True
while inputError: while inputError:
match input("Möchtest du das das Lindenmayer-System das ganze Fenster ausfüllt? [J/n]: ").lower(): match input("Möchtest du, dass das Lindenmayer-System das ganze Fenster ausfüllt? [J/n]: ").lower():
case "j"|"": case "j"|"": # Ja ist die Standartantwort.
inputError = False 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) 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) takeInput(turtleObject) # ruft sich rekursiv auf, um den nächsten Befehl entgegen zu nehmen
case "n": case "n":
inputError = False inputError = False
pos = [] 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): def afterClick(vec):
pos.append(vec) pos.append(vec)
if len(pos) == 2: if len(pos) == 2: # Wenn zwei Punkte angeklickt wurden
# Nach einem Klick wird die Funktion nicht mehr aufgerufen
turtleObject.screen.onclick(None) turtleObject.screen.onclick(None)
size = pos[1] - pos[0] from0to1 = pos[1] - pos[0] # Vektor von Punkt Index 0 zu Punkt Index 1
middle = pos[0] + (1/2) * size middle = pos[0] + (1/2) * from0to1 # Mittelpunkt der beiden angeklickten Punkte
size = turtle.Vec2D(abs(size[0]), abs(size[1])) size = turtle.Vec2D(abs(from0to1[0]), abs(from0to1[1]))
drawer.draw(lSystem, recursionDepth, middle, rotation, size, color) drawer.draw(lSystem, recursionDepth, middle, rotation, size, color) # zeichnet das Lindenmayer-System
takeInput(turtleObject) 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))) 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")
case "b": case "b":
# fragt nach einer Hintergrundfarbe und ändert diese entsprechend.
backgroundColor = inputColor(turtleObject, "Hintergrundfarbe eingeben") backgroundColor = inputColor(turtleObject, "Hintergrundfarbe eingeben")
turtleObject.screen.bgcolor(backgroundColor) turtleObject.screen.bgcolor(backgroundColor)
case "q": case "q":
quit() sys.exit() # beendet das Programm
case "l": case "l":
if len(drawer.drawings) == 0: if len(drawer.drawings) == 0:
print("Es gibt nichts, was man löschen könnte.") print("Es gibt nichts, was man löschen könnte.")
else: else:
# gibt die angezeigten Lindenmayer-Systeme aus
for i, drawing in enumerate(drawer.drawings): for i, drawing in enumerate(drawer.drawings):
print(f"{i}: {drawing.info.lSystem.name} Position: {drawing.info.position}") 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) 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) drawer.delete(iDelete) # löscht das Lindenmayer-System
case "s": case "s":
# fragt nach dem Dateipfad. Wenn bereits eine Datei geladen oder gespeichert wurde,
# wird der dazugehörige Pfad als Standartwert verwendet.
filepath = inputString("Dateipfad zum Speichern eingeben", loadedFilepath) filepath = inputString("Dateipfad zum Speichern eingeben", loadedFilepath)
drawingInfo = [drawing.info for drawing in drawer.drawings] loadedFilepath = filepath
save = Save(backgroundColor, drawingInfo)
# 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: try:
file = open(filepath, "wb") file = open(filepath, "wb")
pickle.dump(save, file) pickle.dump(save, file) # schreibt die Daten in die Datei
file.close() file.close()
# infomiert den Nutzer, dass seine Daten gespeichert wurden
print("erfolgreich gespeichert") print("erfolgreich gespeichert")
except Exception as err: except Exception as err:
print(err) print(err) # gibt mögliche IO-Fehler aus
case "r": case "r":
filepath = inputString("Datei, die geladen werden soll eingeben") filepath = inputString("Datei, die geladen werden soll eingeben")
loadedFilepath = filepath
try: try:
file = open(filepath, "rb") file = open(filepath, "rb")
save = pickle.load(file) save = pickle.load(file) # liest die Datei
file.close() file.close()
turtleObject.screen.clear() turtleObject.screen.clear() # leert das Fenster
# ändert die Hintergrundfarbe
backgroundColor = save.backgroundColor backgroundColor = save.backgroundColor
turtleObject.screen.bgcolor(save.backgroundColor) turtleObject.screen.bgcolor(save.backgroundColor)
# zeichnet die Lindenmayer-Systeme
for drawingInfo in save.drawingInfos: for drawingInfo in save.drawingInfos:
drawing = drawer.Drawing(drawingInfo, drawer.newTurtle()) drawing = drawer.Drawing(drawingInfo, drawer.newTurtle())
drawer.drawScaled(drawing) drawer.drawScaled(drawing)
except Exception as err: except Exception as err:
print(err) print(err) # gibt mögliche IO-Fehler aus
case "e": 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 # https://stackoverflow.com/questions/34777676/how-to-convert-a-python-tkinter-canvas-postscript-file-to-an-image-file-readable
canvas = turtleObject.screen.getcanvas() canvas = turtleObject.screen.getcanvas()
postscript = canvas.postscript(colormode = "color") postscript = canvas.postscript(colormode = "color") # exportiert das Bild im postscript Format
image = Image.open(io.BytesIO(postscript.encode("utf-8"))) 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") filepath = inputString("Dateipfad für das zu exportierende Bild eingeben. Das Bildformat wird über die Dateiendung bestimmt")
try: try:
image.save(filepath) image.save(filepath) # speichert das Bild
print("Bild gespeichert") print("Bild gespeichert")
except ValueError: except ValueError: # PIL löst einen ValueError aus, wenn die Dateiendung unbekannt ist
print("Fehler: unbekannte Dateiendung") print("Fehler: unbekannte Dateiendung")
except Exception as err: except Exception as err:
print(err) print(err) # gibt andere Fehler aus
case _: case _:
print("unbekannter Befehl") 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": if inputValue != "d":
takeInput(turtleObject) takeInput(turtleObject)

View File

@ -1,6 +1,7 @@
from dataclasses import dataclass from dataclasses import dataclass
import typing import typing
# beschreibt ein Lindenmayer-System
@dataclass @dataclass
class LSytem: class LSytem:
name: str name: str
@ -9,12 +10,17 @@ class LSytem:
angel: float angel: float
recursionDepth: int recursionDepth: int
# dem Programm bekannte Lindenmayer-Systeme
LSystems = [ LSystems = [
# Lindenmayer-Systeme vom AB
LSytem("toter Busch", "F", {"F": "F[+F]F[-F]F"}, 25.7, 5), 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("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("Laubbaum", "F", {"F": "FF-[-F+F+F]+[+F-F-F]"}, 22.5, 4),
LSytem("AB d", "X", {"X": "F[+X]F[-X]+X", "F": "FF"}, 20.0, 7), LSytem("dürrer Strauch", "X", {"X": "F[+X]F[-X]+X", "F": "FF"}, 20.0, 7),
LSytem("AB e", "X", {"X": "F[+X][-X]FX", "F": "FF"}, 25.7, 7), LSytem("sysmetrisches Pflänzchen", "X", {"X": "F[+X][-X]FX", "F": "FF"}, 25.7, 7),
LSytem("AB f", "X", {"X": "F-[[X]+X]+F[+FX]-X", "F": "FF"}, 22.5, 5), 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("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),
] ]