Fixed g-code arc parse/plot

This commit is contained in:
Juan Pablo Caram
2014-01-08 01:46:50 -05:00
parent 3cb9e444c0
commit 145496b4ae
9 changed files with 546 additions and 423 deletions

402
camlib.py
View File

@@ -7,7 +7,7 @@ import cairo
#import os
#import sys
from numpy import arctan2, Inf, array
from numpy import arctan2, Inf, array, sqrt, pi, ceil, sin, cos
from matplotlib.figure import Figure
# See: http://toblerity.org/shapely/manual.html
@@ -16,6 +16,7 @@ from shapely.geometry import MultiPoint, MultiPolygon
from shapely.geometry import box as shply_box
from shapely.ops import cascaded_union
from descartes.patch import PolygonPatch
class Geometry:
def __init__(self):
@@ -117,8 +118,6 @@ class Gerber (Geometry):
'''
for region in self.regions:
if region['polygon'].is_valid == False:
#polylist = fix_poly(region['polygon'])
#region['polygon'] = fix_poly3(polylist)
region['polygon'] = region['polygon'].buffer(0)
def buffer_paths(self):
@@ -232,6 +231,9 @@ class Gerber (Geometry):
"aperture":last_path_aperture})
def do_flashes(self):
'''
Creates geometry for Gerber flashes (aperture on a single point).
'''
self.flash_geometry = []
for flash in self.flashes:
aperture = self.apertures[flash['aperture']]
@@ -263,6 +265,81 @@ class Gerber (Geometry):
[poly['polygon'] for poly in self.regions] +
self.flash_geometry)
class Excellon(Geometry):
def __init__(self):
Geometry.__init__(self)
self.tools = {}
self.drills = []
def parse_file(self, filename):
efile = open(filename, 'r')
estr = efile.readlines()
efile.close()
self.parse_lines(estr)
def parse_lines(self, elines):
'''
Main Excellon parser.
'''
current_tool = ""
for eline in elines:
## Tool definitions ##
# TODO: Verify all this
indexT = eline.find("T")
indexC = eline.find("C")
indexF = eline.find("F")
# Type 1
if indexT != -1 and indexC > indexT and indexF > indexF:
tool = eline[1:indexC]
spec = eline[indexC+1:indexF]
self.tools[tool] = spec
continue
# Type 2
# TODO: Is this inches?
#indexsp = eline.find(" ")
#indexin = eline.find("in")
#if indexT != -1 and indexsp > indexT and indexin > indexsp:
# tool = eline[1:indexsp]
# spec = eline[indexsp+1:indexin]
# self.tools[tool] = spec
# continue
# Type 3
if indexT != -1 and indexC > indexT:
tool = eline[1:indexC]
spec = eline[indexC+1:-1]
self.tools[tool] = spec
continue
## Tool change
if indexT == 0:
current_tool = eline[1:-1]
continue
## Drill
indexX = eline.find("X")
indexY = eline.find("Y")
if indexX != -1 and indexY != -1:
x = float(int(eline[indexX+1:indexY])/10000.0)
y = float(int(eline[indexY+1:-1])/10000.0)
self.drills.append({'point':Point((x,y)), 'tool':current_tool})
continue
print "WARNING: Line ignored:", eline
def create_geometry(self):
self.solid_geometry = []
sizes = {}
for tool in self.tools:
sizes[tool] = float(self.tools[tool])
for drill in self.drills:
poly = Point(drill['point']).buffer(sizes[drill['tool']]/2.0)
self.solid_geometry.append(poly)
self.solid_geometry = cascaded_union(self.solid_geometry)
class CNCjob:
def __init__(self, units="in", kind="generic", z_move = 0.1,
feedrate = 3.0, z_cut = -0.002):
@@ -279,7 +356,7 @@ class CNCjob:
self.feedminutecode = "G94"
self.absolutecode = "G90"
# Output G-Code
# Input/Output G-Code
self.gcode = ""
# Bounds of geometry given to CNCjob.generate_from_geometry()
@@ -393,6 +470,7 @@ class CNCjob:
self.gcode += "M05\n" # Spindle stop
def create_gcode_geometry(self):
steps_per_circ = 20
'''
G-Code parser (from self.gcode). Generates dictionary with
single-segment LineString's and "kind" indicating cut or travel,
@@ -415,26 +493,42 @@ class CNCjob:
current['Z'] = gobj['Z']
if 'G' in gobj:
current['G'] = gobj['G']
current['G'] = int(gobj['G'])
if 'X' in gobj or 'Y' in gobj:
x = 0
y = 0
kind = ["C","F"] # T=travel, C=cut, F=fast, S=slow
if 'X' in gobj:
x = gobj['X']
else:
x = current['X']
if 'Y' in gobj:
y = gobj['Y']
else:
y = current['Y']
if current['Z'] > 0:
kind[0] = 'T'
if current['G'] == 1:
if current['G'] > 0:
kind[1] = 'S'
geometry.append({'geom':LineString([(current['X'],current['Y']),
(x,y)]), 'kind':kind})
arcdir = [None, None, "cw", "ccw"]
if current['G'] in [0,1]: # line
geometry.append({'geom':LineString([(current['X'],current['Y']),
(x,y)]), 'kind':kind})
if current['G'] in [2,3]: # arc
center = [gobj['I'] + current['X'], gobj['J'] + current['Y']]
radius = sqrt(gobj['I']**2 + gobj['J']**2)
start = arctan2( -gobj['J'], -gobj['I'])
stop = arctan2(-center[1]+y, -center[0]+x)
geometry.append({'geom':arc(center, radius, start, stop,
arcdir[current['G']],
steps_per_circ),
'kind':kind})
# Update current instruction
for code in gobj:
@@ -477,153 +571,46 @@ class CNCjob:
ax.add_patch(patch)
return fig
class Excellon(Geometry):
def __init__(self):
Geometry.__init__(self)
self.tools = {}
self.drills = []
def parse_file(self, filename):
efile = open(filename, 'r')
estr = efile.readlines()
efile.close()
self.parse_lines(estr)
def parse_lines(self, elines):
def plot2(self, axes, tooldia=None, dpi=75, margin=0.1,
color={"T":["#F0E24D", "#B5AB3A"], "C":["#5E6CFF", "#4650BD"]},
alpha={"T":0.3, "C":1.0}):
'''
Main Excellon parser.
Plots the G-code job onto the given axes
'''
current_tool = ""
for eline in elines:
if tooldia == None:
tooldia = self.tooldia
## Tool definitions ##
# TODO: Verify all this
indexT = eline.find("T")
indexC = eline.find("C")
indexF = eline.find("F")
# Type 1
if indexT != -1 and indexC > indexT and indexF > indexF:
tool = eline[1:indexC]
spec = eline[indexC+1:indexF]
self.tools[tool] = spec
continue
# Type 2
# TODO: Is this inches?
#indexsp = eline.find(" ")
#indexin = eline.find("in")
#if indexT != -1 and indexsp > indexT and indexin > indexsp:
# tool = eline[1:indexsp]
# spec = eline[indexsp+1:indexin]
# self.tools[tool] = spec
# continue
# Type 3
if indexT != -1 and indexC > indexT:
tool = eline[1:indexC]
spec = eline[indexC+1:-1]
self.tools[tool] = spec
continue
## Tool change
if indexT == 0:
current_tool = eline[1:-1]
continue
## Drill
indexX = eline.find("X")
indexY = eline.find("Y")
if indexX != -1 and indexY != -1:
x = float(int(eline[indexX+1:indexY])/10000.0)
y = float(int(eline[indexY+1:-1])/10000.0)
self.drills.append({'point':Point((x,y)), 'tool':current_tool})
continue
print "WARNING: Line ignored:", eline
#fig = Figure(dpi=dpi)
#ax = fig.add_subplot(111)
#ax.set_aspect(1)
#xmin, ymin, xmax, ymax = self.input_geometry_bounds
#ax.set_xlim(xmin-margin, xmax+margin)
#ax.set_ylim(ymin-margin, ymax+margin)
def create_geometry(self):
self.solid_geometry = []
sizes = {}
for tool in self.tools:
sizes[tool] = float(self.tools[tool])
for drill in self.drills:
poly = Point(drill['point']).buffer(sizes[drill['tool']]/2.0)
self.solid_geometry.append(poly)
self.solid_geometry = cascaded_union(self.solid_geometry)
class motion:
'''
Represents a machine motion, which can be cutting or just travelling.
'''
def __init__(self, start, end, depth, typ='line', offset=None, center=None,
radius=None, tooldia=0.5):
self.typ = typ
self.start = start
self.end = end
self.depth = depth
self.center = center
self.radius = radius
self.tooldia = tooldia
self.offset = offset # (I, J)
if tooldia == 0:
for geo in self.G_geometry:
linespec = '--'
linecolor = color[geo['kind'][0]][1]
if geo['kind'][0] == 'C':
linespec = 'k-'
x, y = geo['geom'].coords.xy
axes.plot(x, y, linespec, color=linecolor)
else:
for geo in self.G_geometry:
poly = geo['geom'].buffer(tooldia/2.0)
patch = PolygonPatch(poly, facecolor=color[geo['kind'][0]][0],
edgecolor=color[geo['kind'][0]][1],
alpha=alpha[geo['kind'][0]], zorder=2)
axes.add_patch(patch)
def gparse1(filename):
'''
Parses G-code file into list of dictionaries like
Examples: {'G': 1.0, 'X': 0.085, 'Y': -0.125},
{'G': 3.0, 'I': -0.01, 'J': 0.0, 'X': 0.0821, 'Y': -0.1179}
'''
f = open(filename)
gcmds = []
for line in f:
line = line.strip()
# Remove comments
# NOTE: Limited to 1 bracket pair
op = line.find("(")
cl = line.find(")")
if op > -1 and cl > op:
#comment = line[op+1:cl]
line = line[:op] + line[(cl+1):]
# Parse GCode
# 0 4 12
# G01 X-0.007 Y-0.057
# --> codes_idx = [0, 4, 12]
codes = "NMGXYZIJFP"
codes_idx = []
i = 0
for ch in line:
if ch in codes:
codes_idx.append(i)
i += 1
n_codes = len(codes_idx)
if n_codes == 0:
continue
# Separate codes in line
parts = []
for p in range(n_codes-1):
parts.append( line[ codes_idx[p]:codes_idx[p+1] ].strip() )
parts.append( line[codes_idx[-1]:].strip() )
# Separate codes from values
cmds = {}
for part in parts:
cmds[part[0]] = float(part[1:])
gcmds.append(cmds)
f.close()
return gcmds
def gparse1b(gtext):
'''
gtext is a single string with g-code
'''
gcmds = []
lines = gtext.split("\n")
lines = gtext.split("\n") # TODO: This is probably a lot of work!
for line in lines:
line = line.strip()
@@ -662,98 +649,43 @@ def gparse1b(gtext):
cmds[part[0]] = float(part[1:])
gcmds.append(cmds)
return gcmds
def gparse2(gcmds):
x = []
y = []
z = []
xypoints = []
motions = []
current_g = None
for cmds in gcmds:
# Destination point
x_ = None
y_ = None
z_ = None
if 'X' in cmds:
x_ = cmds['X']
x.append(x_)
if 'Y' in cmds:
y_ = cmds['Y']
y.append(y_)
if 'Z' in cmds:
z_ = cmds['Z']
z.append(z_)
# Ingnore anything but XY movements from here on
if x_ is None and y_ is None:
#print "-> no x,y"
continue
if x_ is None:
x_ = xypoints[-1][0]
if y_ is None:
y_ = xypoints[-1][1]
if z_ is None:
z_ = z[-1]
mot = None
if 'G' in cmds:
current_g = cmds['G']
if current_g == 0: # Fast linear
if len(xypoints) > 0:
#print "motion(", xypoints[-1], ", (", x_, ",", y_, "),", z_, ")"
mot = motion(xypoints[-1], (x_, y_), z_)
if current_g == 1: # Feed-rate linear
if len(xypoints) > 0:
#print "motion(", xypoints[-1], ", (", x_, ",", y_, "),", z_, ")"
mot = motion(xypoints[-1], (x_, y_), z_)
if current_g == 2: # Clockwise arc
if len(xypoints) > 0:
if 'I' in cmds and 'J' in cmds:
mot = motion(xypoints[-1], (x_, y_), z_, offset=(cmds['I'],
cmds['J']), typ='arccw')
if current_g == 3: # Counter-clockwise arc
if len(xypoints) > 0:
if 'I' in cmds and 'J' in cmds:
mot = motion(xypoints[-1], (x_, y_), z_, offset=(cmds['I'],
cmds['J']), typ='arcacw')
if mot is not None:
motions.append(mot)
xypoints.append((x_, y_))
x = array(x)
y = array(y)
z = array(z)
xmin = min(x)
xmax = max(x)
ymin = min(y)
ymax = max(y)
print "x:", min(x), max(x)
print "y:", min(y), max(y)
print "z:", min(z), max(z)
print xypoints[-1]
def get_bounds(geometry_sets):
xmin = Inf
ymin = Inf
xmax = -Inf
ymax = -Inf
return xmin, xmax, ymin, ymax, motions
#geometry_sets = [self.gerbers, self.excellons]
for gs in geometry_sets:
for g in gs:
gxmin, gymin, gxmax, gymax = g.solid_geometry.bounds
xmin = min([xmin, gxmin])
ymin = min([ymin, gymin])
xmax = max([xmax, gxmax])
ymax = max([ymax, gymax])
return [xmin, ymin, xmax, ymax]
def arc(center, radius, start, stop, direction, steps_per_circ):
da_sign = {"cw":-1.0, "ccw":1.0}
points = []
if direction=="ccw" and stop <= start:
stop += 2*pi
if direction=="cw" and stop >= start:
stop -= 2*pi
angle = abs(stop - start)
#angle = stop-start
steps = max([int(ceil(angle/(2*pi)*steps_per_circ)), 2])
delta_angle = da_sign[direction]*angle*1.0/steps
for i in range(steps+1):
theta = start + delta_angle*i
points.append([center[0]+radius*cos(theta), center[1]+radius*sin(theta)])
return LineString(points)
############### cam.py ####################
def coord(gstr,digits,fraction):
'''