cyclone-pcb-factory/Software/CycloneHost/GcodeParser.py

229 lines
7.0 KiB
Python

#!/usr/bin/python
# AUTHOR:
# Carlosgs (http://carlosgs.es)
# LICENSE:
# Attribution - Share Alike - Creative Commons (http://creativecommons.org/licenses/by-sa/3.0/)
#
# DISCLAIMER:
# This software is provided "as is", and you use the software at your own risk. Under no
# circumstances shall Carlosgs be liable for direct, indirect, special, incidental, or
# consequential damages resulting from the use, misuse, or inability to use this software,
# even if Carlosgs has been advised of the possibility of such damages.
#
# CREDIT:
# Based on Etch_Z_adjust.1.8.py from http://www.cnczone.com/forums/pcb_milling/82628-cheap_simple_height-probing.html (multiple authors)
# Begin modules
import os.path
# End modules
def parseGcodeRaw(filePath, etch_definition = 0, close_shapes = 0): # Gcode parser from Etch_Z_adjust.1.8.py (modified by Carlosgs to output toolpaths)
gcode_maxXY = (0,0)
gcode_minXY = (0,0)
travel_moves = []
etch_moves = []
if os.path.isfile(filePath) == False :
return etch_moves, travel_moves, gcode_minXY, gcode_maxXY
gcode = open(filePath, "r")
# Height to consider etching
# etch_definition = 0
# Check for max and min values in the gcode file
is_first_X = True
is_first_Y = True
is_first_Z = True
G_dest = 0
X_dest = 0
Y_dest = 0
Z_dest = 10
F_dest = 10
path = []
def get_num(line,char_ptr,num_chars):
char_ptr=char_ptr+1
numstr = ''
good = '-.0123456789'
while char_ptr < num_chars:
digit = line[char_ptr]
if good.find(digit) != -1:
numstr = numstr + digit
char_ptr = char_ptr + 1
else: break
return numstr
def test_X(X_min, X_max):
if X_dest < X_min : X_min = X_dest
elif X_dest > X_max : X_max = X_dest
return X_min, X_max
def test_Y(Y_min, Y_max):
if Y_dest < Y_min : Y_min = Y_dest
elif Y_dest > Y_max : Y_max = Y_dest
return Y_min, Y_max
def isSame(list1, list2): # Compare two lists, returns True if they have same values
i = 0
for val1 in list1:
val2 = list2[i]
if val1 != val2:
return False
i = i + 1
return True
etchMove = False
currentLine = 0.0
lines = gcode.readlines()
totalLines = len(lines)
for line in lines:# check each line
currentLine = currentLine + 1
#print("({0:.1f}%)".format((currentLine / totalLines)*100), "Reading:", line[:-1])
X_start = X_dest
Y_start = Y_dest
Z_start = Z_dest
# check each character
char_ptr = 0
num_chars= len(line)
while char_ptr < num_chars:
char = line[char_ptr]
if '(;'.find(char) != -1:
break
elif char == 'G' :
G_dest = int(get_num(line,char_ptr,num_chars))
elif char == 'X' :
X_dest = float(get_num(line,char_ptr,num_chars))
elif char == 'Y' :
Y_dest = float(get_num(line,char_ptr,num_chars))
elif char == 'Z' :
Z_dest = float(get_num(line,char_ptr,num_chars))
elif char == 'F' :
F_dest = float(get_num(line,char_ptr,num_chars))
char_ptr = char_ptr + 1
if G_dest == 0 or G_dest == 1 :
if Z_dest < etch_definition: # if the line is an etch move, then replace the line with an etch call
#line = 'O200 call [%.4f] [%.4f] [%.4f] [%.4f]\n' % (X_start, Y_start, X_dest, Y_dest)
if etchMove == False :
travel_moves.append(path)
path = []
etchMove = True # Set etch mode
path.append([X_dest,Y_dest,Z_dest,F_dest])
destPoint = [X_dest,Y_dest,Z_dest,F_dest]
if len(path) == 0 or isSame(destPoint,path[-1]) == False: # Don't add same point twice
path.append(destPoint)
# and now check for max and min X and Y values
if is_first_X == True :
X_min = X_dest
X_max = X_dest
is_first_X = False
else : (X_min, X_max) = test_X(X_min, X_max)
if is_first_Y == True :
Y_min = Y_dest
Y_max = Y_dest
is_first_Y = False
else : (Y_min, Y_max) = test_Y(Y_min, Y_max)
else :
if etchMove == True :
if close_shapes : # Return to the start point
path.append(path[0])
etch_moves.append(path)
path = []
etchMove = False # Set travel mode
path.append([X_dest,Y_dest,Z_dest,F_dest])
destPoint = [X_dest,Y_dest,Z_dest,F_dest]
if len(path) == 0 or isSame(destPoint,path[-1]) == False: # Don't add same point twice
path.append(destPoint)
#file_out.append(line)
if is_first_X == False :
# then there were etch moves so get to work!
gcode_maxXY = [X_max, Y_max]
gcode_minXY = [X_min, Y_min]
print("Gcode XY min: " + str(gcode_minXY))
print("Gcode XY max: " + str(gcode_maxXY))
else :
print("No etch moves found!")
return etch_moves, travel_moves, [0,0], [0,0]
gcode.close()
return etch_moves, travel_moves, gcode_minXY, gcode_maxXY
def optimize(etch_moves_in, origin=[0,0], travel_height = 5): # Optimizes the toolpath using closest neighbour (author: Carlosgs)
etch_moves = []
travel_moves = []
if len(etch_moves_in) == 0 :
return etch_moves, travel_moves
travel_moves = []
toolPosition = [origin[0], origin[1], travel_height, 10]
minDistance = 1e9
while len(etch_moves_in) > 0 : # While there are remaining moves
closest = 1e9
distance = 1e9
closestMove_i = 0
i = 0
reverse = 0
for path in etch_moves_in : # Find the one that begins more close to the position of our tool
firstPoint = path[0]
distance = (toolPosition[0]-firstPoint[0])**2 + (toolPosition[1]-firstPoint[1])**2 # We only check XY
if distance < closest :
closest = distance
closestMove_i = i
elif path[-1][2] == path[0][2]: # We also consider that paths can be made in reverse # Only in case there is no Z lift
firstPoint = path[-1] # We check the last point first
distance = (toolPosition[0]-firstPoint[0])**2 + (toolPosition[1]-firstPoint[1])**2 # We only check XY
if distance < closest :
closest = distance
closestMove_i = i
reverse = 1 # Flag set to reverse the path
#print("Using a reverse path did optimize!")
i = i + 1
path = etch_moves_in[closestMove_i] # Select the closest that has been found
if reverse :
path = path[::-1] # If the closest one was from the end, we reverse the path
#print("Reverse!")
firstPoint = path[0]
if distance > 0.1**2 : # This will join etching moves closer than 0.1 mm (this avoids repetitive drill lifting)
travel_moves.append([toolPosition, [firstPoint[0], firstPoint[1], travel_height, firstPoint[3]] ]) # Travel to the initial point of the etching
else :
travel_moves.append([toolPosition, firstPoint]) # Travel to the initial point of the etching (without lifting)
print("Joining etching paths!") # TODO: This needs to join also the paths in etch_moves! otherwise it makes no difference!
if distance < minDistance :
minDistance = distance
etch_moves.append(path) # Do the etching
etch_moves_in.pop(closestMove_i) # Remove the move from the list, it has been done!
toolPosition = path[-1] # Set our endpoint as the initial one for the next move
print("Minimum XY travel distance: " + str(minDistance**0.5) )
travel_moves.append([toolPosition, [origin[0], origin[1], travel_height, 10]]) # Return to the origin
return etch_moves, travel_moves