Sirius3 hat geschrieben:@miracle173: wenn ...
@Sirius
Danke für die konstruktive Kritik. Allerdings bin ich nicht mit allem einverstanden, darum möchte ich auf deine Kritikpunkte im Einzelnen eingehen.
+wenn man 9 Einrückebenen hat, dann ist das ein deutliches Zeichen dafür, dass man eigentlich mehr Funktionen benötigt.
Da hast du recht. Ich habe keine verwendet, da sich nichts an Code wiederholt. Man kann zwar zwei Einzüge vermeiden, indem man statt
if ein
if/break verwendet, aber das will ich eigentlich nicht. Durch Verwendung von aussagekräftigen Funktionen erhöht sich ja auch wesentlich die Lesbarkeit des Codes und macht den Kommentar dann völlig überflüssig. Ich war da aber zu bequem.
Allerdings ist die Implementation mit Hilfe der
contextmanager-Annotation und des
yield Befehls jenseits meiner derzeitigen Sprachkenntnisse, und vermutlich auch der des OP, darum werde ich das vorläufig nicht verwenden. Ich werde mich damit, sobald es mir möglich ist, auseinander setzen.
+readlines ist überflüssig, da direkt über ein Fileobjekt iteriert werden kann.
Der Grund warum ich diese Programm geschrieben habe, war der folgende Satz in einem anderen Post
BlackJack hat geschrieben:...
Auch über die Zeilen einer Textdatei kann man direkt über das Dateiobjekt iterieren ohne die mit einer Methode vorher komplett in den Speicher laden zu müssen...
Ich wollte untersuchen, wie man in Python das implementiert. Ich habe da offenbar nicht ausreichend recherchiert.
readline soll natürlich entfallen.
+Ein String der mit split in eine Liste konvertiert wird, besteht immer aus mindestens einem Element, so dass die if-Abfrage danach immer True ergibt.
Das habe ich übersehen. Eigentlich will ich überprüfen, ob
line bzw
code leer ist. Das sollte ich auch tun, und nicht die Ergebnisliste der Splitoperation, die auch beim Split eines Leerstrings ein Element, nämlich einen Leerstring, enthält.
-Bei der Konstruktion mit der Value-Liste machst Du Dir Dein Leben unnötig schwer. Entweder gibt es eine Regel, dass bestimmte Argumente nur eine Zahl als Parameter haben, dann sollte diese Bedingung gleich geprüft werden, oder man definiert die Parameter aller Argumente als Liste, weil man später ja sonst prüfen müßte, ob das entsprechende Argument eine Liste ist, also nochmal eine Fallunterscheidung machen, um die erste wieder rückgängig zu machen.
Nein, das stimmt nicht. Ich mache mir nicht das Leben schwer, das ist einfach notwendig. Wenn du dir diese Zeilen aus der Beispieldatei anschaust
Code: Alles auswählen
G00 X0.00 Y.00 F100 ( Move to origin )
G00 X-1,500 Y-1,500 F100
siehst du, dass es keine Regel gibt und dass beim gleichen Kommando Parameter sowohl zweidimensionale Vektoren als auch Skalare sein können. Warum auch nicht? Warum soll man später keine Fallunterscheidung machen? Das ist nichts schlechtes. Ziel des Parsers ist es, die Syntax in Python-geeignete Strukturen zu transformieren, ohne all zu viel Wissen über die gcode-Syntax zu wissen. Dieses Wissen kann dann in den einzelnen Funktionen verwendet werden, um die Strukturen noch einmal zu überprüfen.
+Statt der langen if-elif-Kette für die einzelnen Funktionen böte sich ein Wörterbuch an.
Das ist eine sehr gute Idee. Die Liste wird vermutlich auch noch wesentlich länger.
-Den Typ einer Variable sollte man nicht in den Namen schreiben, der kann sich nämlich mit der Entwicklung ändern und dann müsste man an allen Stellen die Variable umbenennen.
Ich denke du sprichst von den Variablen
gint und
gfloat. Das sehe ich nicht so. Der Name ergibt sich aus ihrer
Funktion und nicht aus ihrem Typ. Die Funktion von
gint ist es, einen "regular expression" für zu speichern, der eine gcode-Ganzzahl beschreibt.
Das bedeutet, die Namen sind so gut und sollen so bleiben.
-def parse_arguments(arguments):
Deine Parse-Funktion muss ich ganz verwerfen. Das schaut vielleicht phytonesk aus, bringt aber nichts. Ersten will ich die Unterscheidung in Vektoren und Skalare haben. Da könnte man natürlich noch eine Schritt ans Ende deiner Funktion anhängen, der dies tut. Also eindimensionale Listen durch durch den Wert ihres einzigen Elements ersetzt. Aber dass du alles auf float konvertierst, das ist nicht sinnvoll. Der Parser soll nicht Informationen verschwinden lassen. Wenn irgendwas als Ganzahl eingegeben wird, dann soll es auch als Ganzzahl gelesen und weitergegeben werden und nicht als Fließkomazahl.
Es wäre auch nicht sinnvoll, zu versuchen einen gelesenen Wert auf
int zu konvertieren und wenn dies eine Exception auslöst, zu versuchen den Wert auf
float zu konvertieren. Denn nicht Python bestimmt was eine gcode-Ganzzahl bzw. eine gcode-Fließkommazahl ist, sondern die gcode-Syntax. Ich kenne die zwar auch nicht, behaupte aber, dass eine gcode-Ganzzahl das Format "^[+-]?[0-9]+$" und eine gcode-Fliesskommazahl das Format "^[+-]?(([0-9]+\.)|([0-9]*\.[0-9]+))$" hat. Und darum will ich das auch vorher überprüfen. Python würde z.B. auch
'1.245E-3' als gültige Fließkommazahl ansehen, gcode (bzw. ich) will das nicht. Glücklicherweise werden alle von mir zugelassenen gcode-Formate auch als gültige Python-Formate anerkannt so dass ich mirt
int() bzw. und
float() konvertieren kann.
So ende ich schließlich bei folgendem Programm
Code: Alles auswählen
import re
INPUTFILE ='./gcode.txt'
gint=re.compile('^[+-]?[0-9]+$')
gfloat=re.compile('^[+-]?(([0-9]+\.)|([0-9]*\.[0-9]+))$')
def do_G00(var_list):
print('function G00',var_list)
def do_G01(var_list):
print('function G01',var_list)
def do_G21(var_list):
print('function G21',var_list)
def do_G90(var_list):
print('function G90',var_list)
def do_M02(var_list):
print('function M02',var_list)
def do_M03(var_list):
print('function M03',var_list)
def do_M05(var_list):
print('function M05',var_list)
CMD_TO_FUNC = {
'G00': do_G00,
'G01': do_G01,
'G21': do_G21,
'G90': do_G90,
'M02': do_M02,
'M03': do_M03,
'M05': do_M05,
}
def parse_arguments(code):
parts=code.split()
cmd=parts[0]
var_dict={}
arglist=parts[1:]
for arg in arglist:
var=arg[:1]
vals=arg[1:].split(',')
value_list=[]
for val in vals:
if gint.match(val):
value_list.append(int(val))
elif gfloat.match(val):
value_list.append(float(val))
elif val=='':
pass
else:
raise RuntimeError("value error %s" % val)
if not value_list:
var_dict[var]=None
elif len(value_list)==1:
var_dict[var]=value_list[0]
else:
var_dict[var]=value_list
return([cmd,var_dict])
with open(INPUTFILE, 'r') as f:
for line in f:
line=line.rstrip()
if line:
parts=line.split('(')
code=parts[0]
if code:
[cmd,var_dict]=parse_arguments(code)
print('line:',line)
try:
func = CMD_TO_FUNC[cmd]
except KeyError:
raise RuntimeError("Unknown command %s" % cmd)
else:
func(var_dict)
Hier noch die Testdaten:
Code: Alles auswählen
( This file created by CNC_profile.rb )
( Cutter Diameter: 0.11811023622047245 )
( Stock Size: 4.0551181102362195 x 4.055118110236214 x 0.3937007874015748 )
G21 ( Unit of measure: millimeter )
G90 ( Absolute programming )
M03 ( Spindle on [clockwise] )
G00 Z13,000 F30 (Move safety height)
G00 X0.00 Y.00 F100 ( Move to origin )
G00 X-1,500 Y-1,500 F100
G01 Z0.0 F15
G01 X-1,500 Y-1,500 Z0,000 F50
G01 X-1,500 Y101,500 Z0,000 F50
G01 X101,500 Y101,500 Z0,000 F50
G01 X101,500 Y-1,500 Z0,000 F50
G01 X-1,500 Y-1,500 Z0,000 F50
G00 Z13,000 F30
G00 X0.00 Y.00 F100( Move to origin )
M05 ( Spindle stop )
M02 ( End of program )
G00 Z F30