@jojo45: Anmerkungen zum Quelltext aus dem ersten Beitrag: Das ist Python 2 Quelltext. Python 2 sollte man nicht mehr verwenden, das hat sein Lebensende erreicht/überschritten.
Eingerückt wird vier Leerzeichen pro Ebene, nicht zwei.
Da sind mindestens zwei unnötige Semikolons an Zeilenenden drin.
Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).
`input` ist der Name einer eingebauten Funktion, den sollte man nicht für etwas anderes verwenden.
Einbuchstabige Namen sind selten gute Namen. Es gibt wenige Ausnahmen wie `x`, `y`, und `z` für Koordinaten und `i`, `j`, und `k` für ganze Zahlen als Laufvariablen und für Indexzugriffe wenn es da nichts sprechenderes für gibt. Aber `f`, `o`, oder `l` für Dateiobjekte oder Zeichenketten sind keine gute Idee.
Abkürzungen sollte man sich auch sparen wenn die nicht allgemein verständlich sind. Man sollte nicht `cmds` schreiben wenn man `commands` meint.
`run()` ist als Name ein bisschen sehr generisch. Selbst `convert()` wäre das noch, würde aber immerhin besser beschreiben was die Funktion macht.
`result` wird ganz am Anfang mit einer leeren Zeichenkette belegt die aber nirgends verwendet wird. Das kann und sollte man sich sparen.
Dateien die man öffnet, sollte man auch wieder schliessen. Das wird bei beiden Dateien nicht getan. Am besten öffnet man Dateien zusammen mit der ``with``-Anweisung, damit die Datei auch in jedem Fall wieder geschlossen wird, egal wo und warum der ``with``-Block verlassen wird.
`readlines()` ist nicht nötig. Dateiobjekte selbst sind iterierbar und liefern die Zeilen, und es gibt keinen Grund die Eingabedatei erst komplett in den Arbeitsspeicher zu lesen bevor die Verarbeitunge beginnt.
Bei Textdateien sollte man beim öffnen immer die Kodierung angeben.
`posx`, und `posy` werden nicht wirklich verwendet. Das heisst die Namen werden zwar an Werte gebunden, aber diese Werte werden dann nirgends verwendet.
`split()` wenn man genau zwei Ergebnisse erwartet ist eher ein `partition()`.
In der `createObject()`-Funktion dann das gleiche mit `string` was in der `run()`-Funktion schon mal war: eine leere Zeichenkette die nie verwendet wird.
Zeichenketten in einer Schleife immer wieder mit ``+`` oder ``+=`` zu erweitern ist ineffizient, weil Zeichenketten nicht veränderbar sind, und da immer länger werdende Zeichenketten immer wieder in neue Speicherbereiche kopiert werden müssen. Idiomatisches Python sammelt die Teilzeichenketten in einer Liste und erstellt daraus dann am Ende eine Zeichenkette mit der `join()`-Methode auf Zeichenketten.
"G00" und "G01" kann man auch als ein Kommando abbilden, das den Status als Argument mitführt. Und statt magischer Zahlen würde man sich mindestens Konstanten für die Kommandos definieren.
Zeichenketten und Werte mit ``+`` zusammenstückeln ist eher BASIC denn Python. Python hat dafür Zeichenkettenformatierung mit der `format()`-Methode und f-Zeichenkettenliterale.
Bei den Kommandocodes kann es ja immer nur einen pro Kommando geben, also machen ``elif``-Anweisungen Sinn, und es ist in der Regel eine gute Idee dann auch einen ``else``-Zweig zu haben, damit bei Programmierfehlern keine unbekannten Werte einfach so geräuschlos unter den Tisch fallen.
Ungetestet:
Code: Alles auswählen
#!/usr/bin/env python3
"""
Simple GCode to Arduino hex format converter.
It only understands G00 and G01 codes, nothing fancy!
It will automatically scale the object to the full 12 bit
range for my Arduino laser project, to change that
you have to modify the scale in `create_object()`.
Typical files I worked with have been generated with
http://ncplot.com/stickfont/stickfont.htm (StickFont 1.1)
Usage: python convert_gcode.py inputfile.nc outputfile.cpp
"""
import math
import sys
from enum import Enum, auto
from pathlib import Path
class CommandType(Enum):
LASER_STATE = auto()
COORDINATE = auto()
def create_object(name, commands):
min_x = min_y = math.inf
max_x = max_y = -math.inf
for command_type, arguments in commands:
if command_type is CommandType.COORDINATE:
x, y = arguments
min_x = min(min_x, x)
min_y = min(min_y, y)
max_x = max(max_x, x)
max_y = max(max_y, y)
scale = 1500 / max(max_x - min_x, max_y - min_y)
# scale to the laser range
# velkost objektu max 4095, smal 500 // laser.setScale(7)
print("bounding box x: ", min_x, max_x)
print("bounding box y: ", min_y, max_y)
print("scale: ", scale)
parts = [f"const unsigned short draw_{name}[] PROGMEM = {{\n"]
laser_state_mask = 0
for command_type, arguments in commands:
if command_type is CommandType.LASER_STATE:
laser_state_mask = 0x8000 if arguments[0] else 0
elif command_type is CommandType.COORDINATE:
x, y = arguments
x = int(math.floor((x - min_x) * scale)) | laser_state_mask
y = int(math.floor((y - min_y) * scale))
parts.append(f"0x{x:x},0x{y:x},\n")
else:
raise ValueError(
f"unknown command type {command_type!r}"
f" with arguments {arguments!r}"
)
parts.append("};\n")
return "".join(parts)
def convert(gcode_file_path, cpp_file_path):
drawing = False
commands = []
for line in gcode_file_path.open("r", encoding="ascii"):
if line.startswith("G00"):
if drawing:
commands.append((CommandType.LASER_STATE, (False,)))
drawing = False
elif line.startswith("G01"):
drawing = True
commands.append((CommandType.LASER_STATE, (True,)))
elif line.startswith("X"):
x_part, _, y_part = line[1:].partition("Y")
commands.append(
(CommandType.COORDINATE, (float(x_part), float(y_part)))
)
cpp_file_path.write_text(create_object("object", commands), "ascii")
def main():
if len(sys.argv) < 3:
print("Usage: convert_gcode.py inputfile.nc outputfile.cpp")
else:
convert(Path(sys.argv[1]), Path(sys.argv[2]))
if __name__ == "__main__":
main()
In einer objektorientierten Programmiersprache wie Python, wäre IMHO ein objektorientierte Lösung statt der Tupel mit den Kommandotypen etwas netter.