Datei auslesen und damit Linien zeichnen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
MaxPotter
User
Beiträge: 3
Registriert: Montag 4. November 2019, 10:35

Hallo,
Ich habe gerade erst mit Python angefangen und möchte gerne X und Y Koordinaten aus einer Datei auslesen und als Striche visuell darstellen.

Meine Datei sieht so aus:

PU;
PA 205, 2270;
PD;
PA 233, 2270;
PU;
PA 205, 2320;
PD;
PA 205, 2270;
PU;
PA 205, 2470;
PD;
PA 205, 2420;
PU;
PA 219, 2270;
PD;
PA 219, 2470;
PU;
PA 233, 2270;
PD;
PA 233, 2320;
...

Die Zahlen oben und unten von dem PD gehören jeweils zusammen.

Alle Striche gehören zusammen und müssen auf ein Bild.

Habt ihr Tipps oder sogar ein beispiel Programm für mich ?
Benutzeravatar
__blackjack__
User
Beiträge: 14133
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@MaxPotter: Das sieht aus als wenn man es ziemlich einfach nach Turtle-Grafik umsetzen kann — "PU" ist „pen up“, "PD" ist „pen down“, und "PA" bewegt die Schildkröte zu dem angegeben Punkt. Je nach dem ob der Stift auf dem Papier ist oder nicht, wird dabei eine Linie gezeichnet.

So würde ich das auch vom Grundsatz her angehen wenn man das ohne das `turtle`-Modul machen möchte. Also nicht versuchen eine PD-Zeile zu finden und sich dann die davor und danach anzuschauen, sondern die einfach der Reihe nach abarbeiten und sich den jeweiligen Zustand (Stift auf Papier?, aktuelle Koordinate, nächste Koordinate) merken.

Und das übliche beim Programmieren: ein grosses Problem in kleinere Teilprobleme zerlegen, und die wieder in Teilprobleme zerlegen, solange bis die einzelnen Teilprobleme so klein sind, dass man sie mit einer einfachen Funktion mit wenigen Zeilen lösen kann. Diese jeweilige Teillösung testen, und wenn sie funktioniert, mit der nächsten Teillösung weiter machen, solange bis man eine Gesamtlösung hat.
“It is easier to change the specification to fit the program than vice versa.” — Alan J. Perlis
Benutzeravatar
/me
User
Beiträge: 3561
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

MaxPotter hat geschrieben: Montag 4. November 2019, 10:56 Habt ihr Tipps oder sogar ein beispiel Programm für mich ?
Du öffnest die Datei und liest sie zeilenweise durch. Anhand der ersten zwei Zeichen kannst du feststellen, was getan werden muss. PU und PD klingt übrigens nach "Pen Up" und "Pen Down". Wenn du PU hattest ist der Startpunkt der Linie durch die nächste PA-Zeile gegeben. Den Startpunkt merkst du dir und wenn dann nach einem PD die nächste PA-Zeile kommst kannst du deinen Strich zeichnen. Falls du nicht garantieren kannst, dass sich PU und PD immer abwechseln müsstest du den aktuellen Zielpunkt auch noch als neuen Startpunkt setzen.

Die Frage ist, mit welcher GUI du da umsetzen möchtest. Turtle-Grafik könnte hier eine Option sein.
MaxPotter
User
Beiträge: 3
Registriert: Montag 4. November 2019, 10:35

/me hat geschrieben: Montag 4. November 2019, 11:59
MaxPotter hat geschrieben: Montag 4. November 2019, 10:56 Habt ihr Tipps oder sogar ein beispiel Programm für mich ?
Du öffnest die Datei und liest sie zeilenweise durch. Anhand der ersten zwei Zeichen kannst du feststellen, was getan werden muss. PU und PD klingt übrigens nach "Pen Up" und "Pen Down". Wenn du PU hattest ist der Startpunkt der Linie durch die nächste PA-Zeile gegeben. Den Startpunkt merkst du dir und wenn dann nach einem PD die nächste PA-Zeile kommst kannst du deinen Strich zeichnen. Falls du nicht garantieren kannst, dass sich PU und PD immer abwechseln müsstest du den aktuellen Zielpunkt auch noch als neuen Startpunkt setzen.

Die Frage ist, mit welcher GUI du da umsetzen möchtest. Turtle-Grafik könnte hier eine Option sein.
Naja da ich X und Y Koordinaten habe dachte ich, ich mach es mit tkinter. Mein momentanes Problem ist das einzelne einlesen der Zeilen und die zuordnung welche zahl jetzt X und welche Y ist.
Benutzeravatar
/me
User
Beiträge: 3561
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Das Einlesen der Zeilen ist einfach.

Code: Alles auswählen

with open('hier dateiname eintragen') as lines:
    for line in lines:
        print(line)
Jetzt hast du jeweils eine einzelne Zeile und kannst die analysieren. Die split-Methode eines Strings kann dir da gute Dienste leisten. Reguläre Ausdrücke gehen natürlich auch, aber damit machen wir ein neues Fass auf.
Benutzeravatar
__blackjack__
User
Beiträge: 14133
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@MaxPotter: Wie gesagt: Auf Teilprobleme runterbrechen. Das einlesen der Zeilen ist ganz einfach weil Dateiobjekte iterierbar sind und dabei die einzelnen Zeilen liefern.

Code: Alles auswählen

#!/usr/bin/env python3


def main():
    with open("test.txt", encoding="ascii") as lines:
        for line in lines:
            print(line)


if __name__ == "__main__":
    main()
Im nächsten Schritt überlegt man sich dann welche Informationen in den Zeilen stecken und wie man eine Zeile in eine möglichst gleichförmige Datenstruktur überführen kann die diese Informationen enthält. Das wäre beispielsweise ein Tupel aus dem Kommando und einer Liste mit den Zahlen. Wobei nicht jedes Kommando Zahlen hat — dann bleibt die Liste leer. Sie sollte aber trotzdem da sein, das vereinfacht das weiterverarbeiten. Dazu schreibt man sich dann eine Funktion die aus einer gegeben Zeile so ein Tupel erstellt.

Code: Alles auswählen

#!/usr/bin/env python3

COMMAND_NAME_TO_ARGUMENT_COUNT = {"PA": 2, "PD": 0, "PU": 0}


def parse_line(line):
    """
    Parse given input line into a tupel of command name and arguments.
    
    Raises:
        ValueError: if the line doesn't end with ";", or the argument(s) can't
            be parsed as numbers, or has the wrong number of arguments.

        KeyError: if the command name is unknown.
    
    Examples:
        >>> parse_line("PU;")
        ('PU', [])
        >>> parse_line("PA 23, 42;")
        ('PA', [23, 42])
    """
    #
    # TODO Hier kommt der Code hin der das macht was der Docstring beschreibt.
    #
    return (name, arguments)


def main():
    with open("test.txt", encoding="ascii") as lines:
        for name, arguments in map(parse_line, lines):
            print(f"Kommando: {name!r}, Argumente: {arguments!r}")


if __name__ == "__main__":
    main()
Die Ausgabe für Deine Beispieleingabe sieht dann so aus:

Code: Alles auswählen

Kommando: 'PU', Argumente: []
Kommando: 'PA', Argumente: [205, 2270]
Kommando: 'PD', Argumente: []
Kommando: 'PA', Argumente: [233, 2270]
Kommando: 'PU', Argumente: []
Kommando: 'PA', Argumente: [205, 2320]
Kommando: 'PD', Argumente: []
Kommando: 'PA', Argumente: [205, 2270]
Kommando: 'PU', Argumente: []
Kommando: 'PA', Argumente: [205, 2470]
Kommando: 'PD', Argumente: []
Kommando: 'PA', Argumente: [205, 2420]
Kommando: 'PU', Argumente: []
Kommando: 'PA', Argumente: [219, 2270]
Kommando: 'PD', Argumente: []
Kommando: 'PA', Argumente: [219, 2470]
Kommando: 'PU', Argumente: []
Kommando: 'PA', Argumente: [233, 2270]
Kommando: 'PD', Argumente: []
Kommando: 'PA', Argumente: [233, 2320]
Als nächstes muss man sich überlegen wie man mit dem Strom von (name, argumente)-Tupeln dem Ziel mit den Linien näher kommt. Eine Linie wird durch ihre beiden Endpunkte beschrieben. Liesse sich als Tupel mit Koordinatentupeln beschreiben: ((x₁, y₁), (x₂, y₂)). Also könnte man eine Funktion gebrauchen die aus einer Folge von Kommandotupeln, eine Folge von Linienkoordinatentupel erstellt.

Code: Alles auswählen

#!/usr/bin/env python3

COMMAND_NAME_TO_ARGUMENT_COUNT = {"PA": 2, "PD": 0, "PU": 0}


def parse_line(line):
    """
    Parse given input line into a tupel of command name and arguments.
    
    Raises:
        ValueError: if the line doesn't end with ";", or the argument(s) can't
            be parsed as numbers, or has the wrong number of arguments.

        KeyError: if the command name is unknown.
    
    Examples:
        >>> parse_line("PU;")
        ('PU', [])
        >>> parse_line("PA 23, 42;")
        ('PA', [23, 42])
    """
    #
    # TODO Hier kommt der Code hin der das macht was der Docstring beschreibt.
    #
    return (name, arguments)


def process_commands(commands):
    """
    Turn iterable of commands into an iterable of line data in the form of
    tuples with start and end points: ((x₁, y₁), (x₂, y₂)).
    
    Examples:
        >>> list(process_commands([("PD", []), ("PA", [10, 20])]))
        [((0, 0), (10, 20))]
        >>> commands = [("PU", []), ("PA", [10, 20]), ("PD", []), ("PA", [30, 40])]
        >>> list(process_commands(commands))
        [((10, 20), (30, 40))]
    """
    #
    # TODO Hier kommt der Code hin der das macht was der Docstring beschreibt.
    #


def main():
    with open("test.txt", encoding="ascii") as lines:
        for line in process_commands(map(parse_line, lines)):
            print(f"Linie: {line!r}")


if __name__ == "__main__":
    main()
Die Ausgabe von dem Programm sieht dann so aus:

Code: Alles auswählen

Linie: ((205, 2270), (233, 2270))
Linie: ((205, 2320), (205, 2270))
Linie: ((205, 2470), (205, 2420))
Linie: ((219, 2270), (219, 2470))
Linie: ((233, 2270), (233, 2320))
Und dann wäre man auch schon dabei GUI-Code zu schreiben der dann Linien auf einem `tkinter.Canvas` zeichnet.
“It is easier to change the specification to fit the program than vice versa.” — Alan J. Perlis
MaxPotter
User
Beiträge: 3
Registriert: Montag 4. November 2019, 10:35

@__blackjack__
Erstmal vielen danke für die tolle Antwort, nur leider verstehe ich davon nicht so viel.
Okay also alles in kleine schritte runter brechen:

Dann wehre mein Plan Das ich jede Zeile einzeln einlese und dann bei jeder Zeile einzeln die ersten beiden Buchstaben anschaue.
Damit ich dann sagen kann, wenn PD dann schaue die zahlen darüber an und benutze sie als x1 und y1 und dann schaue die zahlen darunter an und benutze sie als x2 und y2.
Daraufhin kann ich dann mit den x1,y1 und x2,y2 Koordinaten in "tkinter.Canvas" eine nach der anderen Linie zeichnen.

Soweit der Plan, nur kann ich es einfach nicht umsetzen, ich bin heute schon den dritten tag daran und schaffe es noch nicht mal eine nach der anderen Zeile einzulesen.
Ich dachte ich könne es so machen das ich einfach sage schau dir Zeile 1 an, bearbeite Diese und setzt dann eine variable +1 damit er dann im nächsten Durchgang Zeile 2 bearbeitet.

Es ist auch eigentlich nur ein einmaliges ding weshalb ich auch nicht besonders viel lust habe extra dafür Python zu lernen.
Benutzeravatar
__blackjack__
User
Beiträge: 14133
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@MaxPotter: Von dem Ansatz habe ich ja schon mal abgeraten sich irgendwelche Zeilen davor anschauen zu müssen. Wenn man das so schreibt das man sich immer nur die aktuelle Zeile anschauen muss, wird das einfacher, ohne komische Sonderfälle die man beachten muss, oder hoffen muss, das die nie in den Daten vorkommen können.

Eine nach der anderen Zeile einlesen ist die erste Iteration der Programmentwicklung in meinem Beitrag gewesen und /me hat das gleiche im Beitrag davor schon gezeigt. Und wie gesagt, es reicht, und ist einfacher, sich immer nur die aktuelle Zeile anzuschauen und zu verarbeiten, ohne auf vorhergehende Zeilen oder gar schon auf folgende Zeilen zugreifen zu wollen. Du musst Dir überlegen was Du bei jedem Kommando aus der Datei an Zustand brauchst und wie das Kommando den ändert. "PU" und "PD" ändern den ”Stift”, ob der auf dem Papier ist oder nicht. Das kann man sich mit einem Wahrheitswert merken, den man beispielsweise `is_pen_down` nennen kann. Bei "PU" bekommt der den Wert `False` bei "PD" den Wert `True`. Bei "PA" muss man wenn `is_pen_down` wahr ist eine Linie von der aktuellen Stiftposition zu den Koordinaten hinter dem "PA" ziehen. In jedem Fall, also unabhängig von `is_pen_down`, werden nach Abarbeitung des "PA" die Koordinaten davon zur aktuellen Stiftposition. Das war's eigentlich auch schon um aus Kommandos Linien zu machen.

Im Kern macht das die Generatorfunktion die ich `process_commands()` genannt habe, nur das die keine Zeilen bekommt und Linien zeichnet, sondern die Kommandos und die Liniendaten als Tupel bekommt und liefert. Eben um das als einzelnen, in sich geschlossenen, und leicht separat entwickel- und testbaren Teil zu haben. Die Funktion besteht aus 14 Zeilen Code (und 8 lokale Namen) der im Grunde 1:1 das macht was im letzten Absatz in Worten steht.

Die Anzahl der Namen habe ich erwähnt, weil der Mensch laut Studien so 5 bis 7 Dinge gleichzeitig im Kopf auseinanderhalten kann, was zu der Faustregel führt das man in einer Funktion möglichst nicht mehr als 10 verschiedene Namen haben sollte, weil es sonst schwerer für den Leser wird die Funktion zu verstehen. Und derjenige der den Code schreibt, ist letztendlich ja auch selbst Leser, denn er muss ja verstehen was er da schreibt.

Ich erwähne das weil ich wirklich wichtig finde das gerade Anfänger eher zu viele kleine Funktionen schreiben als zu wenige. Wenn Du mit einer Schleife anfängst die die Zeilen liest, darin Code schreibst der erkennt womit die Zeichenkette anfängt, und in dieser Schleife dann den Zustand des virtuellen Plotters aktualisierst, und gegebenfalls eine Linie auf einem `Canvas` zeichnest, dann wird das am Ende viel zu unübersichtlich und man kann das auch nicht gut testen, weil man immer eine Datei und eine GUI braucht um auch nur *irgendwas* davon zu testen. Um das umwandeln der Kommandos in Linien(daten) zu testen sollte man aber beispielsweise weder eine Datei noch eine GUI benötigen. Das sieht man ja an den Beispielen im Docstring der `process_commands()`-Funktion. Die ist vom Format der Zeilen in der Datei und einer GUI entkoppelt. Der ist egal woher die Daten kommen, das könnten auch Plotterkommandos sein die aus einer Datei mit einem anderen Format kommen. Und der Funktion ist egal was mit den Daten danach gemacht wird.

Wenn man etwas in einer Programmiersprache umsetzen möchte, dann muss man die wohl oder übel lernen. Wüsste nicht wie es anders geht. Wenn Du dazu keine Lust hast, ist die Frage wie gross die Motivation ist das Problem zu lösen und ob man von dieser Motivation etwas ins lernen der Programmiersprache transferieren kann.

Nun habe ich überlegt ob man das Format was Du da hast nicht vielleicht leicht in ein Standardformat wandeln kann für das es schon Betrachter und Konverter gibt, um weniger selbst programmieren zu müssen, und stellte fest, dass Dein Beispiel *verdammt* nach HP-GL aussieht. *Dafür* gibt es schon Software zum anschauen und umwandeln in andere Formate wie Bilder, PDF, SVG. Das freie Vektorgrafik-Programm Inkscape kann beispielsweise HP-GL-Dateien laden.
“It is easier to change the specification to fit the program than vice versa.” — Alan J. Perlis
Benutzeravatar
__blackjack__
User
Beiträge: 14133
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Falls das wirklich HP-GL ist, wäre das selbst programmieren übrigens ein wenig komplizierter, denn das Format ist ein kleines bisschen komplexer als Dein Beispiel vermuten lässt. Ich bin da beispielsweise vom Beispiel her davon ausgegangen das es einen Befehl pro Zeile gibt, und zwischen Befehl und Koordinaten mindestens ein Leerzeichen steht, und das es nur bei "PA" Koordinaten geben kann, und das dort auch nur genau zwei Werte stehen können. Alles Vermutungen die in HP-GL nicht zutreffen. Dein Beispiel kann man in einer Zeile und mit nur einem "PA" auch so schreiben:

PA;PU205,2320;PD205,2270,233,2270,233,2320;PU205,2470;PD205,2420;PU219,2270;PD219,2470;
“It is easier to change the specification to fit the program than vice versa.” — Alan J. Perlis
Benutzeravatar
__blackjack__
User
Beiträge: 14133
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Bin wieder auf diesen Beitrag gestossen weil ich aus aktuellem Anlass nach Linien mit Mausklicks zeichnen gesucht hatte. Ist hier nicht das Thema, aber ich dachte das ist ein Problem bei dem man das ``match``/``case``-Konstrukt benutzen könnte. :-)

Code: Alles auswählen

#!/usr/bin/env python3
import sys
import tkinter as tk
from tkinter.messagebox import showerror

TEXT_LINES = """\
 PU;
PA205,2270;
PD ;
PA 233 , 2270 ;
PU;
PA 205, 2320;
PD;
PA 205, 2270;
PU;
PA 205, 2470;
PD;
PA 205, 2420;
PU;
PA 219, 2270;
PD;
PA 219, 2470;
PU;
PA 233, 2270;
PD;
PA 233, 2320;
""".splitlines()


def main():
    window = tk.Tk()
    canvas = tk.Canvas(window, width=800, height=800, background="white")
    canvas.pack()

    is_pen_down = False
    pen_coordinates = (0, 0)

    for line_number, line in enumerate(TEXT_LINES, 1):
        line = line.strip().rstrip(";")
        command = line[:2]
        arguments_text = line[2:].strip()
        try:
            arguments = (
                tuple(map(int, arguments_text.split(",")))
                if arguments_text
                else ()
            )
        except ValueError:
            showerror(message=f"Invalid number in line {line_number}: {line}")
            sys.exit(1)

        match command, arguments:
            case "PU", ():
                is_pen_down = False

            case "PD", ():
                is_pen_down = True

            case "PA", (x, y):
                new_coordinates = (x, y)
                if is_pen_down:
                    canvas.create_line(
                        pen_coordinates, new_coordinates, width=2
                    )
                pen_coordinates = new_coordinates

            case _:
                showerror(message=f"Error in line {line_number}: {line}")
                sys.exit(1)
    #
    # Coordinates in the example are larger than the (visible) canvas, so move
    # the items to the top left.
    #
    x, y, _, _ = canvas.bbox(tk.ALL)
    canvas.move(tk.ALL, -x, -y)

    window.mainloop()


if __name__ == "__main__":
    main()
“It is easier to change the specification to fit the program than vice versa.” — Alan J. Perlis
Pedroski55
User
Beiträge: 16
Registriert: Freitag 25. Juli 2025, 00:20

Ich habe gerade erst mit Python angefangen
Dann soll es wohl nicht zu kompliziert werden. Wenn man Python benutzt, ist empfohlen eine VENV zuzulegen. Das ist eine geschlossene Umgebung wo man ungestört arbeiten kann. Suche nach Python VENV.

Ich werde nicht schlau daraus, was deine Koordinaten darstellen sollen, drum habe sie nicht benutzt. Ich habe mir Koordinaten für ein Sechseck gegeben, gespeichert in hexagon.txt Sieht so aus:
PU;
PA -50, -87;
PD;
PA 50, -87;
PU;
PA 100, 0;
PD;
PA 50, 87;
PU;
PA -50, 87;
PD;
PA -100, 0;
Mit Python bekommt man ein Pythonshell namens Idle, ganz umsonst. Profis mögen das nicht, aber ich benutze immer Idle, ich finde es toll. Man kann Schritweise alles abarbeiten, bis man weiß, dass es geht. Suche nach Python Idle.

Hier, in Idle natürlich, lese ich die Daten ein und hole mir die Nummern heraus. Python unterscheidet zwischen Nummern und String. Hier, die Nummern sehen wie Nummern aus, sind aber strings.

Probiere dies in Idle: type('1') dann probiere type(1)

Code: Alles auswählen

import regex

# wo sind die Daten? Wenn du Windoze benutzt dann vielleicht
# C:\\users\temp\mypython
#data = '/home/peterr/temp/svg/lines_data.txt'
data = '/home/peterr/temp/svg/hexagon.txt'
# blanko Datei
blanko = '/home/peterr/temp/svg/turtle_blanko.html'
# wo das Resultat speichern?
savename = '/home/peterr/temp/svg/turtle_lines.html'

# öffne die Daten-als-Textdatei
with open(data) as infile:
    points = infile.readlines()

# alle \n loeschen
for i in range(len(points)):
    # alle \n loeschen
    points[i] = points[i].strip()

# für Info über regex: https://www.rexegg.com ist sehr gut             
# Beispiel: line = 'PA -50 -87;'
e = regex.compile(r'([-+]?\d+), ([-+]?\d+)')
# res = e.search(line)
# print(res.groups())

# finde alle xy Punkte
xy = []
for line in points:
    if line == 'PU;' or line == 'PD;':
        continue    
    else:
        print(line)
        res = e.search(line)
        xy.append(res.groups())
Nun nehmen wir die Nummern-als-String, die wir grad geholt haben, und machen einen String, einen Text, der in SVG ein Sechseck darstellt:

Code: Alles auswählen

# Startpunkt x y
s1 = xy[0][0] # '-50'
s2 = xy[0][1] # '-87'

newxy = [f'<polygon class="turtle" points="{s1},{s2}']

for i in range(1, len(xy)):
    x = xy[i][0]
    y = xy[i][1]   
    newxy.append(f'{x},{y} ')

ende = '"/>'
newxy.append(ende)
text = ' '.join(newxy)
Dann laden wir diese html blanko Datei in Python Idle:

Code: Alles auswählen

<!DOCTYPE html>
<html>
  <head>
    <title>Turtle Lines</title>

</head>
<body>
    <h1>Turtle Lines</h1>
    <p>
<svg class="tri" xmlns="http://www.w3.org/2000/svg"
  width="400"
  height="400"
  viewBox="-200 -200 400 400"
>
<defs>
    <style type="text/css"><![CDATA[
 .axes{
		  fill: none;
		  stroke: indigo;
		  stroke-linecap: round;
		  stroke-linejoin: round;
		  stroke-width: 5;
		  stroke-dasharray: 10 20;
  }
.turtle {
		  fill:red;
		  stroke:blue;
  		  stroke-linecap:round;
  		  stroke-linejoin:round;
  		  stroke-width:3;
}
    ]]></style>
  </defs>
<rect  x="-200" y="-200"  width="400" height="400" fill="pink"/>
  <path class="axes" d="M 0 -200 V 400 M -200 0 H 400"/>    	  
  <!-- ein Rechteck zum Vergleich -->
  <polygon class="turtle" points="-50,-50 50,-50 50,50 -50,50" />
  
  POLYGON

</svg>
	</p>
</body>
</html>
Nun, ersetzen wir POLYGON mit dem obengebauten text, speichern und fertig. Dann einfach die so erstellte Datei in deinem Browser öffnen.

Code: Alles auswählen

with open(blanko) as infile:
    html = infile.read()

newhtml = html.replace('POLYGON', text)
with open(savename, 'w') as outfile:
    outfile.write(newhtml)
Die Datei savename nun in deinem Browser öffenen.

In Idle kannst du nun jeden Schritt einzeln ausführen oder abändern, je nach Lust und Laune.

Es gibt sehr viele Infos zum SVG im Internet, hier nur ein Beispiel: https://developer.mozilla.org/en-US/doc ... VG_and_CSS

Schöne Grüße aus Hongkong, wo ein Typhoon in Anmarsch ist!
Sirius3
User
Beiträge: 18299
Registriert: Sonntag 21. Oktober 2012, 17:20

@Pedroski55: vor allem wenn man von einer einfachen Sprache auf Python umsteigt, kann es leicht passieren, dass man Sprachkonstrukte übernimmt, für die es viel bessere Alternativen in Python gibt. Klassiker ist die for-Schleife mit Index, die man so gut wie nie braucht.
Du verwendest das regex-Modul, obwohl re völlig ausreichend wäre.
die Benennung von Variablen ist für das Verständnis wichtig, `data` enthält gar keine Daten, sondern ist ein Dateiname, also `DATA_FILENAME`, groß geschreiben, weil Konstante. Ähnliches gilt für `blanko`. Über `e` wollen wir gar nicht erst reden.
Man verändert den Inhalt einer Listenicht nachträglich, sondern erzeugt einfach eine neue.

Code: Alles auswählen

commands = []
for command in points:
    # loesche alle Leerraumzeichen am Anfang und Ende
    commands.append(command.strip())
Kommentare sollten korrekt sein, Kommentare, die etwas anderes behaupten, als der Code macht, schaden mehr, als sie helfen.
Warum brauchst Du eine Sonderbehandlung für den ersten Punkt? Jetzt mal mit Listcomprehension statt Index:

Code: Alles auswählen

coordinates = [
    f"{x},{y}" for x, y in xy
]
text = f'<polygon class="turtle" points="{" ".join(coordinates)}" />'
Über die Verwendung von Templates hatten wir ja schon im anderen Thread geredet.
Benutzeravatar
__blackjack__
User
Beiträge: 14133
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pedroski55: Ergänzend zu Sirius3: Wenn man Textdateien öffnet, sollte man immer die Kodierung der Datei angeben, sonst kann es passieren dass Python falsch ”rät”. Hier erwarten wird maximal ASCII.

Das Beispiel für eine Zeile bei der Definition des Regulären Ausdrucks passt nicht zu den Daten und dem Ausdruck: Da fehlt das Komma.

``continue`` ist hier unnötig kompliziert so wie das geschrieben ist. Man kann die Bedingung einfach anders herum formulieren und den Inhalt vom ``else``-Zweig in den ``if``-Zweig setzen. Und falls man auf die `print()`-Ausgabe verzichten kann, die ja eher nur zur Fehlersuche da drin ist, dann lässt sich das ganze Konstrukt zu einer „list comprehension“ umformulieren. Wobei man eigentlich auch gar nicht die komplette Datei auf einmal einlesen muss, man kann auch Generatorausdrücke verwenden.

Statt ``line == "PU;" or line == "PD;"`` kann man auch einfach ``line in ["PU;", "PD;"]`` schreiben. Hier fängt das Programm aber schon an fehlerhaft zu werden weil es nicht mehr die ursprünglich gestellte Aufgabe erfüllt. Man kann die Pen-Up und Pen-Down Anweisungen nicht einfach ignorieren und annehmen, dass das Endpunkt jeder Linie gleichzeitig der Startpunkt der nächsten ist. Das stimmt nur in Deinem konstruierten Beispiel, nicht bei den Ausgangsdaten und halt auch nicht allgemein bei dem beschriebenen Eingabeformat.

Es lohnt ein Blick auf das `pathlib`-Modul um die Wiederholungen in den Dateipfaden loszuwerden und wegen der praktischen Methoden um eine komplette Textdatei zu lesen und zu schreiben.

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import re
from pathlib import Path

BASE_PATH = Path("/home/peterr/temp/svg")
INPUT_FILE_PATH = BASE_PATH / "hexagon.txt"
HTML_TEMPLATE_FILE_PATH = BASE_PATH / "turtle_blanko.html"
TARGET_FILE_PATH = BASE_PATH / "turtle_lines.html"


def main():
    #
    # Für Info über regex: https://www.rexegg.com ist sehr gut.
    #
    coordinates_re = re.compile(r"([-+]?\d+), ([-+]?\d+)")
    #
    # Beispiel: line = "PA -50, -87;"
    # match = coordinates_re.search(line)
    # print(match.groups())
    #
    with INPUT_FILE_PATH.open(encoding="ascii") as file:
        lines = (line.strip() for line in file)
        #
        # FIXME Man darf PU und PD nicht einfach ignorieren, dann funktioniert
        #   das nicht mehr mit jeder Eingabe.  Insbesondere auch nicht mit der
        #   Eingabe aus der Aufgabenstellung!
        #
        coordinates = (
            coordinates_re.search(line).groups()
            for line in lines
            if line not in ["PU;", "PD;"]
        )
        points_text = " ".join(f"{x},{y}" for x, y in coordinates)

    TARGET_FILE_PATH.write_text(
        HTML_TEMPLATE_FILE_PATH.read_text(encoding="utf-8").replace(
            "POLYGON", f'<polygon class="turtle" points="{points_text}" />'
        ),
        encoding="utf-8",
    )


if __name__ == "__main__":
    main()
Fehlerbehandlung und ein bisschen mehr Flexibilität beim Eingabeformat wäre natürlich nett. Im Moment ist die Behandlung von Whitespace innerhalt einer Zeile sehr restriktiv, dafür wird auf der anderen Seite überhaupt nicht geprüft ob die Zeilen mit den Koordinaten überhaupt das Kommando PA sind.

Beim HTML/SVG ist die Einrückung nicht ordentlich. Die Zeichnung sollte auch keine `fill`-Farbe haben, denn auch hier ist es ja wieder nur das gewählte Beispiel das zufällig eine Fläche ergibt, das kann man nicht für beliebige Eingaben verallgemeinern.

Code: Alles auswählen

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Turtle Lines</title>
    <meta charset="utf-8">
  </head>
  <body>
    <h1>Turtle Lines</h1>
    <p>
      <svg xmlns="http://www.w3.org/2000/svg"
        width="400" height="400" viewBox="-200 -200 400 400"
      >
        <style>
          .axes {
            fill: none;
            stroke: indigo;
            stroke-linecap: round;
            stroke-linejoin: round;
            stroke-width: 5;
            stroke-dasharray: 10 20;
          }
          .turtle {
            fill: none;
            stroke: blue;
            stroke-linecap: round;
            stroke-linejoin: round;
            stroke-width: 3;
          }
        </style>
        <rect x="-200" y="-200" width="400" height="400" fill="pink"/>
        <path class="axes" d="M 0 -200 V 400 M -200 0 H 400"/>
        <!-- Ein Rechteck zum Vergleich. -->
        <polygon class="turtle" points="-50,-50 50,-50 50,50 -50,50" />
        POLYGON
      </svg>
    </p>
  </body>
</html>
“It is easier to change the specification to fit the program than vice versa.” — Alan J. Perlis
Pedroski55
User
Beiträge: 16
Registriert: Freitag 25. Juli 2025, 00:20

Weswegen seid ihr der Meinung, es gibt nur eine Methode das gewünschte Resultat zu erzielen, und ihr alleine habt den Schlüssel dazu?

Bitte, etwas mehr Beschiedenheit, ja?
Sirius3
User
Beiträge: 18299
Registriert: Sonntag 21. Oktober 2012, 17:20

Es gibt durchaus unterschiedliche Lösungswege. Einen fortgeschrittenen Programmierer zeichnet aus, dass er oder sie verschiedene Weg kennt, und den besten auswählt. Bescheidenheit ist, dass ich das Wissen anderer Annehme und es hier der Allgemeinheit weitertrage, ohne zu behaupten, ich alleine wäre Allwissend.
Benutzeravatar
__blackjack__
User
Beiträge: 14133
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pedroski55: Wie kommst Du darauf das wir dieser Meinung sind? Insbesondere da sich die Teillösungen von Sirius3 und mir auch unterscheiden, können wir doch gar nicht der Meinung sein es gäbe nur eine Methode.

Andererseits sagt das Zen von Python (``import this``) das es vorzugsweise nur einen offensichtlichen Weg geben sollte. Was die Namensschreibweisen angeht, gibt es Konventionen, die auch schriftlich festgehalten sind in Style Guide for Python Code.

Es gibt idiomatischen Code in Python und welchen der in anderen Programmiersprachen idiomatisch ist, in Python aber ungewöhnlich ist. Oft stecken da auch objektive Gründe dahinter.

Und dann ist da halt die Sache das es einfach fehlerhaft ist. Deine Programme führen nicht zum gewünschten Ergebnis. Das mit dem Dreieck hatte einfach nur die Texte im Titel und der Überschrift ersetzt, aber gar nichts an der Zeichnung geändert. Das mit dem Hexagon hat…
  • …falsche Daten, denn die hexagon.txt zeichnet gar kein Hexagon, sondern nur drei Linien.
  • …das Programm ignoriert PD und PU und kann damit korrekte Daten, wie das Beispiel aus der ursprünglichen Fragestellung nicht richtig darstellen.
“It is easier to change the specification to fit the program than vice versa.” — Alan J. Perlis
Benutzeravatar
__blackjack__
User
Beiträge: 14133
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Tja, ich bin natürlich nicht unfehlbar: In HPGL bedeuten niedrigere y-Werte das der Punkt weiter oben liegt. Bei Tk's Canvas ist das umgekehrt. Also hier mal mein letztes Programm korrigiert und das nimmt die Daten jetzt auf der Standardeingabe entgegen, damit man da leichter verschiedene Dateien verwenden kann:

Code: Alles auswählen

#!/usr/bin/env python3
import sys
import tkinter as tk
from tkinter.messagebox import showerror


def main():
    window = tk.Tk()
    canvas = tk.Canvas(window, width=800, height=800, background="white")
    canvas.pack()

    is_pen_down = False
    pen_coordinates = (0, 0)

    for line_number, line in enumerate(sys.stdin, 1):
        line = line.strip().rstrip(";")
        command = line[:2]
        arguments_text = line[2:].strip()
        try:
            arguments = (
                tuple(map(int, arguments_text.split(",")))
                if arguments_text
                else ()
            )
        except ValueError:
            showerror(message=f"Invalid number in line {line_number}: {line}")
            sys.exit(1)

        match command, arguments:
            case "PU", ():
                is_pen_down = False

            case "PD", ():
                is_pen_down = True

            case "PA", (x, y):
                new_coordinates = (x, -y)
                if is_pen_down:
                    canvas.create_line(
                        pen_coordinates, new_coordinates, width=2
                    )
                pen_coordinates = new_coordinates

            case _:
                showerror(message=f"Error in line {line_number}: {line}")
                sys.exit(1)
    #
    # Move all items to the top left to ensure visibility.
    #
    x, y, _, _ = canvas.bbox(tk.ALL)
    canvas.move(tk.ALL, -x, -y)

    window.mainloop()


if __name__ == "__main__":
    main()
Die kaputte hexagon.txt sieht dann so aus:
Bild

Hier mal eine weitere Beispieldatei (python.hpgl):

Code: Alles auswählen

PU;
PA -150, 88;
PD;
PA -150, 136;
PD;
PA -126, 136;
PD;
PA -118, 128;
PD;
PA -118, 120;
PD;
PA -126, 112;
PD;
PA -150, 112;
PU;
PA -102, 120;
PD;
PA -102, 112;
PD;
PA -86, 96;
PU;
PA -70, 120;
PD;
PA -70, 112;
PD;
PA -102, 80;
PU;
PA -54, 120;
PD;
PA -38, 120;
PU;
PA -46, 136;
PD;
PA -46, 88;
PD;
PA -38, 88;
PU;
PA -6, 88;
PD;
PA -6, 136;
PU;
PA -6, 120;
PD;
PA 10, 120;
PD;
PA 18, 112;
PD;
PA 18, 88;
PU;
PA 42, 96;
PD;
PA 42, 112;
PD;
PA 50, 120;
PD;
PA 58, 120;
PD;
PA 66, 112;
PD;
PA 66, 96;
PD;
PA 58, 88;
PD;
PA 50, 88;
PD;
PA 42, 96;
PU;
PA 90, 88;
PD;
PA 90, 120;
PD;
PA 90, 112;
PD;
PA 98, 120;
PD;
PA 106, 120;
PD;
PA 114, 112;
PD;
PA 114, 88;
PU;
PA 154, 88;
PD;
PA 154, 96;
PD;
PA 162, 96;
PD;
PA 162, 88;
PD;
PA 154, 88;
PU;
PA 154, 104;
PD;
PA 154, 136;
PD;
PA 162, 136;
PD;
PA 162, 104;
PD;
PA 154, 104;
Die sieht so aus:
Bild

Wenn man die Datei durch das letzte Programm von Pedroski schickt, kommt das hier dabei heraus:
Bild

Offensichtlich liefert das Programm kein korrektes Ergebnis.
“It is easier to change the specification to fit the program than vice versa.” — Alan J. Perlis
Antworten