Path check.

This commit is contained in:
Juan Pablo Caram
2015-01-19 17:17:16 -05:00
parent 922aa6734d
commit 6361ad3338
2 changed files with 206 additions and 163 deletions

View File

@@ -133,6 +133,10 @@ class App(QtCore.QObject):
json.dump([], f) json.dump([], f)
f.close() f.close()
self.app_home = os.path.dirname(os.path.realpath(__file__))
App.log.debug("Application path is " + self.app_home)
App.log.debug("Started in " + os.getcwd())
QtCore.QObject.__init__(self) QtCore.QObject.__init__(self)
self.ui = FlatCAMGUI(self.version) self.ui = FlatCAMGUI(self.version)
@@ -243,7 +247,8 @@ class App(QtCore.QObject):
"zoom_ratio": 1.5, "zoom_ratio": 1.5,
"point_clipboard_format": "(%.4f, %.4f)", "point_clipboard_format": "(%.4f, %.4f)",
"zdownrate": None, "zdownrate": None,
"excellon_zeros": "L" "excellon_zeros": "L",
"cncjob_coordinate_format": "X%.4fY%.4f"
}) })
############################### ###############################
@@ -1771,7 +1776,8 @@ class App(QtCore.QObject):
# Which objects to update the given parameters. # Which objects to update the given parameters.
routes = { routes = {
"zdownrate": CNCjob, "zdownrate": CNCjob,
"excellon_zeros": Excellon "excellon_zeros": Excellon,
"cncjob_coordinate_format": CNCjob
} }
for param in routes: for param in routes:
@@ -2187,7 +2193,7 @@ class App(QtCore.QObject):
od = collections.OrderedDict(sorted(commands.items())) od = collections.OrderedDict(sorted(commands.items()))
for cmd, val in od.iteritems(): for cmd, val in od.iteritems():
#print cmd, '\n', ''.join(['~']*len(cmd)) #print cmd, '\n', ''.join(['~']*len(cmd))
output += cmd + ' \n' + ''.join(['~']*len(cmd)) + '\n' output += cmd + ' \n' + ''.join(['~'] * len(cmd)) + '\n'
t = val['help'] t = val['help']
usage_i = t.find('>') usage_i = t.find('>')
@@ -2197,7 +2203,7 @@ class App(QtCore.QObject):
output += expl + '\n\n' output += expl + '\n\n'
continue continue
expl = t[:usage_i-1] expl = t[:usage_i - 1]
#print expl + '\n' #print expl + '\n'
output += expl + '\n\n' output += expl + '\n\n'
@@ -2250,8 +2256,25 @@ class App(QtCore.QObject):
return "ERROR: No such system parameter." return "ERROR: No such system parameter."
def set_sys(param, value): def set_sys(param, value):
# tcl string to python keywords:
tcl2py = {
"None": None,
"none": None,
"false": False,
"False": False,
"true": True,
"True": True
}
if param in self.defaults: if param in self.defaults:
try:
value = tcl2py[value]
except KeyError:
pass
self.defaults[param] = value self.defaults[param] = value
self.propagate_defaults() self.propagate_defaults()
return "Ok" return "Ok"

338
camlib.py
View File

@@ -145,6 +145,17 @@ class Geometry(object):
return self.solid_geometry.bounds return self.solid_geometry.bounds
def flatten(self, geometry=None, reset=True, pathonly=False): def flatten(self, geometry=None, reset=True, pathonly=False):
"""
Creates a list of non-iterable linear geometry objects.
Polygons are expanded into its exterior and interiors if specified.
Results are placed in self.flat_geoemtry
:param geometry: Shapely type or list or list of list of such.
:param reset: Clears the contents of self.flat_geometry.
:param pathonly: Expands polygons into linear elements.
"""
if geometry is None: if geometry is None:
geometry = self.solid_geometry geometry = self.solid_geometry
@@ -2166,7 +2177,8 @@ class CNCjob(Geometry):
""" """
defaults = { defaults = {
"zdownrate": None "zdownrate": None,
"coordinate_format": "X%.4fY%.4f"
} }
def __init__(self, units="in", kind="generic", z_move=0.1, def __init__(self, units="in", kind="generic", z_move=0.1,
@@ -2240,43 +2252,44 @@ class CNCjob(Geometry):
return factor return factor
def generate_from_excellon(self, exobj): # def generate_from_excellon(self, exobj):
""" # """
Generates G-code for drilling from Excellon object. # Generates G-code for drilling from Excellon object.
self.gcode becomes a list, each element is a # self.gcode becomes a list, each element is a
different job for each tool in the excellon code. # different job for each tool in the excellon code.
""" # """
self.kind = "drill" # self.kind = "drill"
self.gcode = [] # self.gcode = []
#
t = "G00 X%.4fY%.4f\n" # #t = "G00 X%.4fY%.4f\n"
down = "G01 Z%.4f\n" % self.z_cut # t = "G00 " + CNCjob.defaults["coordinate_format"] + "\n"
up = "G01 Z%.4f\n" % self.z_move # down = "G01 Z%.4f\n" % self.z_cut
# up = "G01 Z%.4f\n" % self.z_move
for tool in exobj.tools: #
# for tool in exobj.tools:
points = [] #
# points = []
for drill in exobj.drill: #
if drill['tool'] == tool: # for drill in exobj.drill:
points.append(drill['point']) # if drill['tool'] == tool:
# points.append(drill['point'])
gcode = self.unitcode[self.units.upper()] + "\n" #
gcode += self.absolutecode + "\n" # gcode = self.unitcode[self.units.upper()] + "\n"
gcode += self.feedminutecode + "\n" # gcode += self.absolutecode + "\n"
gcode += "F%.2f\n" % self.feedrate # gcode += self.feedminutecode + "\n"
gcode += "G00 Z%.4f\n" % self.z_move # Move to travel height # gcode += "F%.2f\n" % self.feedrate
gcode += "M03\n" # Spindle start # gcode += "G00 Z%.4f\n" % self.z_move # Move to travel height
gcode += self.pausecode + "\n" # gcode += "M03\n" # Spindle start
# gcode += self.pausecode + "\n"
for point in points: #
gcode += t % point # for point in points:
gcode += down + up # gcode += t % point
# gcode += down + up
gcode += t % (0, 0) #
gcode += "M05\n" # Spindle stop # gcode += t % (0, 0)
# gcode += "M05\n" # Spindle stop
self.gcode.append(gcode) #
# self.gcode.append(gcode)
def generate_from_excellon_by_tool(self, exobj, tools="all"): def generate_from_excellon_by_tool(self, exobj, tools="all"):
""" """
@@ -2290,7 +2303,9 @@ class CNCjob(Geometry):
:return: None :return: None
:rtype: None :rtype: None
""" """
log.debug("Creating CNC Job from Excellon...") log.debug("Creating CNC Job from Excellon...")
if tools == "all": if tools == "all":
tools = [tool for tool in exobj.tools] tools = [tool for tool in exobj.tools]
else: else:
@@ -2304,10 +2319,9 @@ class CNCjob(Geometry):
points.append(drill['point']) points.append(drill['point'])
log.debug("Found %d drills." % len(points)) log.debug("Found %d drills." % len(points))
#self.kind = "drill"
self.gcode = [] self.gcode = []
t = "G00 X%.4fY%.4f\n" t = "G00 " + CNCjob.defaults["coordinate_format"] + "\n"
down = "G01 Z%.4f\n" % self.z_cut down = "G01 Z%.4f\n" % self.z_cut
up = "G01 Z%.4f\n" % self.z_move up = "G01 Z%.4f\n" % self.z_move
@@ -2329,71 +2343,71 @@ class CNCjob(Geometry):
self.gcode = gcode self.gcode = gcode
def generate_from_geometry(self, geometry, append=True, tooldia=None, tolerance=0): # def generate_from_geometry(self, geometry, append=True, tooldia=None, tolerance=0):
""" # """
Generates G-Code from a Geometry object. Stores in ``self.gcode``. # Generates G-Code from a Geometry object. Stores in ``self.gcode``.
#
Algorithm description: # Algorithm description:
---------------------- # ----------------------
Follow geometry paths in the order they are being read. No attempt # Follow geometry paths in the order they are being read. No attempt
to optimize. # to optimize.
#
:param geometry: Geometry defining the toolpath # :param geometry: Geometry defining the toolpath
:type geometry: Geometry # :type geometry: Geometry
:param append: Wether to append to self.gcode or re-write it. # :param append: Wether to append to self.gcode or re-write it.
:type append: bool # :type append: bool
:param tooldia: If given, sets the tooldia property but does # :param tooldia: If given, sets the tooldia property but does
not affect the process in any other way. # not affect the process in any other way.
:type tooldia: bool # :type tooldia: bool
:param tolerance: All points in the simplified object will be within the # :param tolerance: All points in the simplified object will be within the
tolerance distance of the original geometry. # tolerance distance of the original geometry.
:return: None # :return: None
:rtype: None # :rtype: None
""" # """
if tooldia is not None: # if tooldia is not None:
self.tooldia = tooldia # self.tooldia = tooldia
#
self.input_geometry_bounds = geometry.bounds() # self.input_geometry_bounds = geometry.bounds()
#
if not append: # if not append:
self.gcode = "" # self.gcode = ""
#
# Initial G-Code # # Initial G-Code
self.gcode = self.unitcode[self.units.upper()] + "\n" # self.gcode = self.unitcode[self.units.upper()] + "\n"
self.gcode += self.absolutecode + "\n" # self.gcode += self.absolutecode + "\n"
self.gcode += self.feedminutecode + "\n" # self.gcode += self.feedminutecode + "\n"
self.gcode += "F%.2f\n" % self.feedrate # self.gcode += "F%.2f\n" % self.feedrate
self.gcode += "G00 Z%.4f\n" % self.z_move # Move (up) to travel height # self.gcode += "G00 Z%.4f\n" % self.z_move # Move (up) to travel height
self.gcode += "M03\n" # Spindle start # self.gcode += "M03\n" # Spindle start
self.gcode += self.pausecode + "\n" # self.gcode += self.pausecode + "\n"
#
# Iterate over geometry and run individual methods # # Iterate over geometry and run individual methods
# depending on type # # depending on type
for geo in geometry.solid_geometry: # for geo in geometry.solid_geometry:
#
if type(geo) == Polygon: # if type(geo) == Polygon:
self.gcode += self.polygon2gcode(geo, tolerance=tolerance) # self.gcode += self.polygon2gcode(geo, tolerance=tolerance)
continue # continue
#
if type(geo) == LineString or type(geo) == LinearRing: # if type(geo) == LineString or type(geo) == LinearRing:
self.gcode += self.linear2gcode(geo, tolerance=tolerance) # self.gcode += self.linear2gcode(geo, tolerance=tolerance)
continue # continue
#
if type(geo) == Point: # if type(geo) == Point:
self.gcode += self.point2gcode(geo) # self.gcode += self.point2gcode(geo)
continue # continue
#
if type(geo) == MultiPolygon: # if type(geo) == MultiPolygon:
for poly in geo: # for poly in geo:
self.gcode += self.polygon2gcode(poly, tolerance=tolerance) # self.gcode += self.polygon2gcode(poly, tolerance=tolerance)
continue # continue
#
log.warning("G-code generation not implemented for %s" % (str(type(geo)))) # log.warning("G-code generation not implemented for %s" % (str(type(geo))))
#
# Finish # # Finish
self.gcode += "G00 Z%.4f\n" % self.z_move # Stop cutting # self.gcode += "G00 Z%.4f\n" % self.z_move # Stop cutting
self.gcode += "G00 X0Y0\n" # self.gcode += "G00 X0Y0\n"
self.gcode += "M05\n" # Spindle stop # self.gcode += "M05\n" # Spindle stop
def generate_from_geometry_2(self, geometry, append=True, tooldia=None, tolerance=0): def generate_from_geometry_2(self, geometry, append=True, tooldia=None, tolerance=0):
""" """
@@ -2412,6 +2426,7 @@ class CNCjob(Geometry):
assert isinstance(geometry, Geometry) assert isinstance(geometry, Geometry)
## Flatten the geometry ## Flatten the geometry
# Only linear elements (no polygons) remain.
flat_geometry = geometry.flatten(pathonly=True) flat_geometry = geometry.flatten(pathonly=True)
log.debug("%d paths" % len(flat_geometry)) log.debug("%d paths" % len(flat_geometry))
@@ -2456,11 +2471,14 @@ class CNCjob(Geometry):
# deletion will fail. # deletion will fail.
storage.remove(geo) storage.remove(geo)
# If last point in geometry is the nearest
# then reverse coordinates.
if list(pt) == list(geo.coords[-1]): if list(pt) == list(geo.coords[-1]):
#print "Reversing"
geo.coords = list(geo.coords)[::-1] geo.coords = list(geo.coords)[::-1]
# G-code # G-code
# Note: self.linear2gcode() and self.point2gcode() will
# lower and raise the tool every time.
if type(geo) == LineString or type(geo) == LinearRing: if type(geo) == LineString or type(geo) == LinearRing:
self.gcode += self.linear2gcode(geo, tolerance=tolerance) self.gcode += self.linear2gcode(geo, tolerance=tolerance)
elif type(geo) == Point: elif type(geo) == Point:
@@ -2536,7 +2554,7 @@ class CNCjob(Geometry):
# Separate codes in line # Separate codes in line
parts = [] parts = []
for p in range(n_codes-1): for p in range(n_codes - 1):
parts.append(line[codes_idx[p]:codes_idx[p+1]].strip()) parts.append(line[codes_idx[p]:codes_idx[p+1]].strip())
parts.append(line[codes_idx[-1]:].strip()) parts.append(line[codes_idx[-1]:].strip())
@@ -2712,55 +2730,55 @@ class CNCjob(Geometry):
# TODO: This takes forever. Too much data? # TODO: This takes forever. Too much data?
self.solid_geometry = cascaded_union([geo['geom'] for geo in self.gcode_parsed]) self.solid_geometry = cascaded_union([geo['geom'] for geo in self.gcode_parsed])
def polygon2gcode(self, polygon, tolerance=0): # def polygon2gcode(self, polygon, tolerance=0):
""" # """
Creates G-Code for the exterior and all interior paths # Creates G-Code for the exterior and all interior paths
of a polygon. # of a polygon.
#
:param polygon: A Shapely.Polygon # :param polygon: A Shapely.Polygon
:type polygon: Shapely.Polygon # :type polygon: Shapely.Polygon
:param tolerance: All points in the simplified object will be within the # :param tolerance: All points in the simplified object will be within the
tolerance distance of the original geometry. # tolerance distance of the original geometry.
:type tolerance: float # :type tolerance: float
:return: G-code to cut along polygon. # :return: G-code to cut along polygon.
:rtype: str # :rtype: str
""" # """
#
if tolerance > 0: # if tolerance > 0:
target_polygon = polygon.simplify(tolerance) # target_polygon = polygon.simplify(tolerance)
else: # else:
target_polygon = polygon # target_polygon = polygon
#
gcode = "" # gcode = ""
t = "G0%d X%.4fY%.4f\n" # t = "G0%d X%.4fY%.4f\n"
path = list(target_polygon.exterior.coords) # Polygon exterior # path = list(target_polygon.exterior.coords) # Polygon exterior
gcode += t % (0, path[0][0], path[0][1]) # Move to first point # gcode += t % (0, path[0][0], path[0][1]) # Move to first point
#
if self.zdownrate is not None: # if self.zdownrate is not None:
gcode += "F%.2f\n" % self.zdownrate # gcode += "F%.2f\n" % self.zdownrate
gcode += "G01 Z%.4f\n" % self.z_cut # Start cutting # gcode += "G01 Z%.4f\n" % self.z_cut # Start cutting
gcode += "F%.2f\n" % self.feedrate # gcode += "F%.2f\n" % self.feedrate
else: # else:
gcode += "G01 Z%.4f\n" % self.z_cut # Start cutting # gcode += "G01 Z%.4f\n" % self.z_cut # Start cutting
#
for pt in path[1:]: # for pt in path[1:]:
gcode += t % (1, pt[0], pt[1]) # Linear motion to point # gcode += t % (1, pt[0], pt[1]) # Linear motion to point
gcode += "G00 Z%.4f\n" % self.z_move # Stop cutting # gcode += "G00 Z%.4f\n" % self.z_move # Stop cutting
for ints in target_polygon.interiors: # Polygon interiors # for ints in target_polygon.interiors: # Polygon interiors
path = list(ints.coords) # path = list(ints.coords)
gcode += t % (0, path[0][0], path[0][1]) # Move to first point # gcode += t % (0, path[0][0], path[0][1]) # Move to first point
#
if self.zdownrate is not None: # if self.zdownrate is not None:
gcode += "F%.2f\n" % self.zdownrate # gcode += "F%.2f\n" % self.zdownrate
gcode += "G01 Z%.4f\n" % self.z_cut # Start cutting # gcode += "G01 Z%.4f\n" % self.z_cut # Start cutting
gcode += "F%.2f\n" % self.feedrate # gcode += "F%.2f\n" % self.feedrate
else: # else:
gcode += "G01 Z%.4f\n" % self.z_cut # Start cutting # gcode += "G01 Z%.4f\n" % self.z_cut # Start cutting
#
for pt in path[1:]: # for pt in path[1:]:
gcode += t % (1, pt[0], pt[1]) # Linear motion to point # gcode += t % (1, pt[0], pt[1]) # Linear motion to point
gcode += "G00 Z%.4f\n" % self.z_move # Stop cutting # gcode += "G00 Z%.4f\n" % self.z_move # Stop cutting
return gcode # return gcode
def linear2gcode(self, linear, tolerance=0): def linear2gcode(self, linear, tolerance=0):
""" """
@@ -2781,7 +2799,8 @@ class CNCjob(Geometry):
target_linear = linear target_linear = linear
gcode = "" gcode = ""
t = "G0%d X%.4fY%.4f\n" #t = "G0%d X%.4fY%.4f\n"
t = "G0%d " + CNCjob.defaults["coordinate_format"] + "\n"
path = list(target_linear.coords) path = list(target_linear.coords)
gcode += t % (0, path[0][0], path[0][1]) # Move to first point gcode += t % (0, path[0][0], path[0][1]) # Move to first point
@@ -2799,7 +2818,8 @@ class CNCjob(Geometry):
def point2gcode(self, point): def point2gcode(self, point):
gcode = "" gcode = ""
t = "G0%d X%.4fY%.4f\n" #t = "G0%d X%.4fY%.4f\n"
t = "G0%d " + CNCjob.defaults["coordinate_format"] + "\n"
path = list(point.coords) path = list(point.coords)
gcode += t % (0, path[0][0], path[0][1]) # Move to first point gcode += t % (0, path[0][0], path[0][1]) # Move to first point
@@ -2906,7 +2926,7 @@ def arc(center, radius, start, stop, direction, steps_per_circ):
:return: The desired arc, as list of tuples :return: The desired arc, as list of tuples
:rtype: list :rtype: list
""" """
# TODO: Resolution should be established by fraction of total length, not angle. # TODO: Resolution should be established by maximum error from the exact arc.
da_sign = {"cw": -1.0, "ccw": 1.0} da_sign = {"cw": -1.0, "ccw": 1.0}
points = [] points = []