diff --git a/lindenmayer/__main__.py b/lindenmayer/__main__.py index 49196c4..291a8aa 100644 --- a/lindenmayer/__main__.py +++ b/lindenmayer/__main__.py @@ -1,10 +1,12 @@ import inputHelper import turtle -import time +import drawer -turtle.tracer(0, 0) -turtleObject = turtle.Turtle() -turtleObject.hideturtle() -turtleObject.screen.colormode(255) -inputHelper.takeInput(turtleObject) -turtleObject.screen.mainloop() +turtleObject = drawer.newTurtle() +screen = turtleObject.screen +screen.colormode(255) +try: + inputHelper.takeInput(turtleObject) +except EOFError: + quit() +screen.mainloop() diff --git a/lindenmayer/drawer.py b/lindenmayer/drawer.py index 2f8453d..b49ab99 100644 --- a/lindenmayer/drawer.py +++ b/lindenmayer/drawer.py @@ -16,21 +16,26 @@ class DrawingInfo: scale: float recursionDepth: int +@dataclass +class Drawing: + info: DrawingInfo + turtle: turtle.Turtle + drawings = [] class Drawer(): - def __init__(self, drawingInfo, turtleObject): - self.startWord = drawingInfo.lSystem.startWord - self.recursionDepth = drawingInfo.recursionDepth - self.productionRules = drawingInfo.lSystem.productionRules - self.angel = drawingInfo.lSystem.angel - self.forwardDistance = drawingInfo.scale - self.turtle = turtleObject + def __init__(self, drawing): + 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 self.turtle.penup() - self.turtle.setposition(drawingInfo.position) - self.turtle.setheading(drawingInfo.rotation) - self.turtle.pendown() - self.turtle.pencolor(drawingInfo.color) + self.turtle.setposition(drawing.info.position) + self.turtle.setheading(drawing.info.rotation) + self.pendown() + self.turtle.pencolor(drawing.info.color) def storeEdges(self): pass @@ -79,9 +84,8 @@ class Edges: return turtle.Vec2D(self.max[0], self.max[1]) class DrawerSimulation(Drawer): - def __init__(self, drawingInfo, turtleObject): - super(DrawerSimulation, self).__init__(drawingInfo, turtleObject) - self.turtle.penup() + def __init__(self, drawing): + super(DrawerSimulation, self).__init__(drawing) self.edges = Edges() def storeEdges(self): @@ -94,9 +98,11 @@ class DrawerSimulation(Drawer): def pendown(self): pass -def draw(turtleObject, lSystem, recursionDepth, middle, rotation, size, color): +def draw(lSystem, recursionDepth, middle, rotation, size, color): drawingInfo = DrawingInfo(lSystem, turtle.Vec2D(0, 0), rotation, color, 1, recursionDepth) - simulatedDraw = DrawerSimulation(drawingInfo, turtleObject) + turtleObject = newTurtle() + drawing = Drawing(drawingInfo, turtleObject) + simulatedDraw = DrawerSimulation(drawing) simulatedDraw.draw() maxVec = simulatedDraw.edges.maxVec() minVec = simulatedDraw.edges.minVec() @@ -105,14 +111,21 @@ def draw(turtleObject, lSystem, recursionDepth, middle, rotation, size, color): yScale = size[1] / distance[1] scale = yScale if xScale > yScale else xScale pos = middle + (-minVec - distance * (1/2)) * scale - drawingInfo.position = pos - drawingInfo.scale = scale - actualDrawer = Drawer(drawingInfo, turtleObject) - actualDrawer.draw() - drawings.append(drawingInfo) + drawing.info.position = pos + drawing.info.scale = scale + drawScaled(drawing) -def redraw(turtleObject): - turtleObject.clear() - for drawing in drawings: - drawer = Drawer(drawing, turtleObject) - drawer.draw() +def newTurtle(): + turtleObject = turtle.Turtle() + turtleObject.hideturtle() + turtleObject._tracer(0, 0) + return turtleObject + +def delete(i): + drawings[i].turtle.clear() + del drawings[i] + +def drawScaled(drawing): + actualDrawer = Drawer(drawing) + actualDrawer.draw() + drawings.append(drawing) diff --git a/lindenmayer/inputHelper.py b/lindenmayer/inputHelper.py index fadbbf6..89e5bc8 100644 --- a/lindenmayer/inputHelper.py +++ b/lindenmayer/inputHelper.py @@ -1,18 +1,35 @@ import drawer import turtle import lSystems +import pickle +from dataclasses import dataclass + +@dataclass +class Save: + backgroundColor: any + drawingInfos: [drawer.DrawingInfo] + +loadedFilepath = None +backgroundColor = "white" + +def defaultValueMsg(defaultValue): + if defaultValue == None: + return "" + return f" (Standartwert: {defaultValue})" + +def inputWithDefault(description, defaultValue): + return input(f"{description}{defaultValueMsg(defaultValue)}: ") def inputNum(inputType, description, rangeErrorMsg, minRange, maxRange, defaultValue = None): - n: int while True: - inputValue = input(description) + inputValue = inputWithDefault(description, defaultValue) try: - n = inputType(inputValue) - inputError = n < minRange or n > maxRange + number = inputType(inputValue) + inputError = number < minRange or number > maxRange if inputError: print(rangeErrorMsg) else: - return n + return number except ValueError: if inputValue == "" and defaultValue != None: return defaultValue @@ -23,28 +40,33 @@ def inputColorError(): def inputColor(turtleObject, question, defaultValue = None): while True: - defaultValueMsg = f" (Standartwert: {defaultValue})" if defaultValue != None else "" - 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}: ") + 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 defaultValue != None: return defaultValue inputColorError() else: - oldPenColor = turtleObject.pencolor() try: color = tuple([int(color) for color in inputValue.split()]) turtleObject.pencolor(color) - turtleObject.pencolor(oldPenColor) return color except: try: turtleObject.pencolor(inputValue) - turtleObject.pencolor(oldPenColor) return inputValue except: inputColorError() +def inputString(question, defaultValue = None): + while True: + inputValue = inputWithDefault(question, defaultValue) + if inputValue != "": + return inputValue + if defaultValue != None: + return defaultValue + def takeInput(turtleObject): + global backgroundColor inputValue = input("Bitte einen Befehl eingeben. h für Hilfe: ") match inputValue: case "h": @@ -52,21 +74,23 @@ def takeInput(turtleObject): d: ein neues Lindenmayer-System zeichnen l: ein Lindenmayer-System löschen b: Hintergrundfarbe ändern -q: Programm beenden""") +q: Programm beenden +s: Lindenmayer-Systeme speichern +r: zuvor gespeicherte Lindenmayer-Systeme wiederherstellen""") case "d": for i, lSystem in enumerate(lSystems.LSystems): 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) lSystem = lSystems.LSystems[lSystemIndex] - recursionDepth = inputNum(int, "Rekursiontiefe des Lindenmayer-Systems eingeben [1-20] (Standartwert: 5): ", "Rekursionstiefe nicht im vorgegebenen Bereich.", 1, 20, 5) - rotation = inputNum(float, "Bitte die Rotation in Grad gegen den Uhrzeigersinn angeben, wobei 0° rechts ist (Standartwert: 90°): ", "nur Gradzahlen von 0 bis 360 werden akzeptiert.", 0, 360, 90) + recursionDepth = inputNum(int, "Rekursiontiefe des Lindenmayer-Systems eingeben [1-20]", "Rekursionstiefe nicht im vorgegebenen Bereich.", 1, 20, 5) + 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") while inputError: match input("Möchtest du das das Lindenmayer-System das ganze Fenster ausfüllt? [J/n]: ").lower(): case "j"|"": inputError = False - drawer.draw(turtleObject, lSystem, recursionDepth, turtle.Vec2D(0, 0), rotation, turtleObject.screen._window_size(), color) + drawer.draw(lSystem, recursionDepth, turtle.Vec2D(0, 0), rotation, turtleObject.screen._window_size(), color) takeInput(turtleObject) case "n": inputError = False @@ -78,14 +102,15 @@ q: Programm beenden""") size = pos[1] - pos[0] middle = pos[0] + (1/2) * size size = turtle.Vec2D(abs(size[0]), abs(size[1])) - drawer.draw(turtleObject, lSystem, recursionDepth, middle, rotation, size, color) + drawer.draw(lSystem, recursionDepth, middle, rotation, size, color) takeInput(turtleObject) turtleObject.screen.onclick(lambda x, y: afterClick(turtle.Vec2D(x, y))) case _: print("Bitte j oder n eingeben") case "b": - turtleObject.screen.bgcolor(inputColor(turtleObject, "Hintergrundfarbe eingeben")) + backgroundColor = inputColor(turtleObject, "Hintergrundfarbe eingeben") + turtleObject.screen.bgcolor(backgroundColor) case "q": quit() case "l": @@ -93,10 +118,37 @@ q: Programm beenden""") print("Es gibt nichts, was man löschen könnte.") else: for i, drawing in enumerate(drawer.drawings): - print(f"{i}: {drawing.lSystem.name} Position: {drawing.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) - del drawer.drawings[iDelete] - drawer.redraw(turtleObject) + drawer.delete(iDelete) + case "s": + filepath = inputString("Dateipfad zum Speichern eingeben", loadedFilepath) + drawingInfo = [drawing.info for drawing in drawer.drawings] + save = Save(backgroundColor, drawingInfo) + try: + file = open(filepath, "wb") + pickle.dump(save, file) + print("erfolgreich gespeichert") + except Exception as err: + print(err) + finally: + file.close() + case "r": + filepath = inputString("Datei, die geladen werden soll eingeben") + save: Save + try: + file = open(filepath, "rb") + save = pickle.load(file) + except Exception as err: + print(err) + finally: + file.close() + turtleObject.screen.clear() + backgroundColor = save.backgroundColor + turtleObject.screen.bgcolor(save.backgroundColor) + for drawingInfo in save.drawingInfos: + drawing = drawer.Drawing(drawingInfo, drawer.newTurtle()) + drawer.drawScaled(drawing) case _: print("unbekannter Befehl") if inputValue != "d":