Cutout generator implemented

This commit is contained in:
Juan Pablo Caram
2014-01-13 01:25:57 -05:00
parent d664d40ad0
commit e6b5fd6632
3 changed files with 387 additions and 147 deletions

251
camlib.py
View File

@@ -7,8 +7,10 @@ from shapely.geometry import MultiPoint, MultiPolygon
from shapely.geometry import box as shply_box from shapely.geometry import box as shply_box
from shapely.ops import cascaded_union from shapely.ops import cascaded_union
# Used for solid polygons in Matplotlib
from descartes.patch import PolygonPatch from descartes.patch import PolygonPatch
class Geometry: class Geometry:
def __init__(self): def __init__(self):
# Units (in or mm) # Units (in or mm)
@@ -18,20 +20,20 @@ class Geometry:
self.solid_geometry = None self.solid_geometry = None
def isolation_geometry(self, offset): def isolation_geometry(self, offset):
''' """
Creates contours around geometry at a given Creates contours around geometry at a given
offset distance. offset distance.
''' """
return self.solid_geometry.buffer(offset) return self.solid_geometry.buffer(offset)
def bounds(self): def bounds(self):
''' """
Returns coordinates of rectangular bounds Returns coordinates of rectangular bounds
of geometry: (xmin, ymin, xmax, ymax). of geometry: (xmin, ymin, xmax, ymax).
''' """
if self.solid_geometry == None: if self.solid_geometry is None:
print "Warning: solid_geometry not computed yet." print "Warning: solid_geometry not computed yet."
return (0,0,0,0) return (0, 0, 0, 0)
if type(self.solid_geometry) == list: if type(self.solid_geometry) == list:
return cascaded_union(self.solid_geometry).bounds return cascaded_union(self.solid_geometry).bounds
@@ -39,33 +41,33 @@ class Geometry:
return self.solid_geometry.bounds return self.solid_geometry.bounds
def size(self): def size(self):
''' """
Returns (width, height) of rectangular Returns (width, height) of rectangular
bounds of geometry. bounds of geometry.
''' """
if self.solid_geometry == None: if self.solid_geometry is None:
print "Warning: solid_geometry not computed yet." print "Warning: solid_geometry not computed yet."
return 0 return 0
bounds = self.bounds() bounds = self.bounds()
return (bounds[2]-bounds[0], bounds[3]-bounds[1]) return (bounds[2]-bounds[0], bounds[3]-bounds[1])
def get_empty_area(self, boundary=None): def get_empty_area(self, boundary=None):
''' """
Returns the complement of self.solid_geometry within Returns the complement of self.solid_geometry within
the given boundary polygon. If not specified, it defaults to the given boundary polygon. If not specified, it defaults to
the rectangular bounding box of self.solid_geometry. the rectangular bounding box of self.solid_geometry.
''' """
if boundary == None: if boundary is None:
boundary = self.solid_geometry.envelope boundary = self.solid_geometry.envelope
return boundary.difference(self.solid_geometry) return boundary.difference(self.solid_geometry)
def clear_polygon(self, polygon, tooldia, overlap = 0.15): def clear_polygon(self, polygon, tooldia, overlap=0.15):
''' """
Creates geometry inside a polygon for a tool to cover Creates geometry inside a polygon for a tool to cover
the whole area. the whole area.
''' """
poly_cuts = [polygon.buffer(-tooldia/2.0)] poly_cuts = [polygon.buffer(-tooldia/2.0)]
while(1): while True:
polygon = poly_cuts[-1].buffer(-tooldia*(1-overlap)) polygon = poly_cuts[-1].buffer(-tooldia*(1-overlap))
if polygon.area > 0: if polygon.area > 0:
poly_cuts.append(polygon) poly_cuts.append(polygon)
@@ -107,12 +109,12 @@ class Gerber (Geometry):
self.flash_geometry = [] self.flash_geometry = []
def fix_regions(self): def fix_regions(self):
''' """
Overwrites the region polygons with fixed Overwrites the region polygons with fixed
versions if found to be invalid (according to Shapely). versions if found to be invalid (according to Shapely).
''' """
for region in self.regions: for region in self.regions:
if region['polygon'].is_valid == False: if not region['polygon'].is_valid:
region['polygon'] = region['polygon'].buffer(0) region['polygon'] = region['polygon'].buffer(0)
def buffer_paths(self): def buffer_paths(self):
@@ -122,98 +124,98 @@ class Gerber (Geometry):
self.buffered_paths.append(path["linestring"].buffer(width/2)) self.buffered_paths.append(path["linestring"].buffer(width/2))
def aperture_parse(self, gline): def aperture_parse(self, gline):
''' """
Parse gerber aperture definition Parse gerber aperture definition
into dictionary of apertures. into dictionary of apertures.
''' """
indexstar = gline.find("*") indexstar = gline.find("*")
indexC = gline.find("C,") indexC = gline.find("C,")
if indexC != -1: # Circle, example: %ADD11C,0.1*% if indexC != -1: # Circle, example: %ADD11C,0.1*%
apid = gline[4:indexC] apid = gline[4:indexC]
self.apertures[apid] = {"type":"C", self.apertures[apid] = {"type": "C",
"size":float(gline[indexC+2:indexstar])} "size": float(gline[indexC+2:indexstar])}
return apid return apid
indexR = gline.find("R,") indexR = gline.find("R,")
if indexR != -1: # Rectangle, example: %ADD15R,0.05X0.12*% if indexR != -1: # Rectangle, example: %ADD15R,0.05X0.12*%
apid = gline[4:indexR] apid = gline[4:indexR]
indexX = gline.find("X") indexX = gline.find("X")
self.apertures[apid] = {"type":"R", self.apertures[apid] = {"type": "R",
"width":float(gline[indexR+2:indexX]), "width": float(gline[indexR+2:indexX]),
"height":float(gline[indexX+1:indexstar])} "height": float(gline[indexX+1:indexstar])}
return apid return apid
indexO = gline.find("O,") indexO = gline.find("O,")
if indexO != -1: # Obround if indexO != -1: # Obround
apid = gline[4:indexO] apid = gline[4:indexO]
indexX = gline.find("X") indexX = gline.find("X")
self.apertures[apid] = {"type":"O", self.apertures[apid] = {"type": "O",
"width":float(gline[indexO+2:indexX]), "width": float(gline[indexO+2:indexX]),
"height":float(gline[indexX+1:indexstar])} "height": float(gline[indexX+1:indexstar])}
return apid return apid
print "WARNING: Aperture not implemented:", gline print "WARNING: Aperture not implemented:", gline
return None return None
def parse_file(self, filename): def parse_file(self, filename):
''' """
Calls Gerber.parse_lines() with array of lines Calls Gerber.parse_lines() with array of lines
read from the given file. read from the given file.
''' """
gfile = open(filename, 'r') gfile = open(filename, 'r')
gstr = gfile.readlines() gstr = gfile.readlines()
gfile.close() gfile.close()
self.parse_lines(gstr) self.parse_lines(gstr)
def parse_lines(self, glines): def parse_lines(self, glines):
''' """
Main Gerber parser. Main Gerber parser.
''' """
path = [] # Coordinates of the current path path = [] # Coordinates of the current path
last_path_aperture = None last_path_aperture = None
current_aperture = None current_aperture = None
for gline in glines: for gline in glines:
if gline.find("D01*") != -1: # pen down if gline.find("D01*") != -1: # pen down
path.append(coord(gline, self.digits, self.fraction)) path.append(coord(gline, self.digits, self.fraction))
last_path_aperture = current_aperture last_path_aperture = current_aperture
continue continue
if gline.find("D02*") != -1: # pen up if gline.find("D02*") != -1: # pen up
if len(path) > 1: if len(path) > 1:
# Path completed, create shapely LineString # Path completed, create shapely LineString
self.paths.append({"linestring":LineString(path), self.paths.append({"linestring": LineString(path),
"aperture":last_path_aperture}) "aperture": last_path_aperture})
path = [coord(gline, self.digits, self.fraction)] path = [coord(gline, self.digits, self.fraction)]
continue continue
indexD3 = gline.find("D03*") indexD3 = gline.find("D03*")
if indexD3 > 0: # Flash if indexD3 > 0: # Flash
self.flashes.append({"loc":coord(gline, self.digits, self.fraction), self.flashes.append({"loc": coord(gline, self.digits, self.fraction),
"aperture":current_aperture}) "aperture": current_aperture})
continue continue
if indexD3 == 0: # Flash? if indexD3 == 0: # Flash?
print "WARNING: Uninplemented flash style:", gline print "WARNING: Uninplemented flash style:", gline
continue continue
if gline.find("G37*") != -1: # end region if gline.find("G37*") != -1: # end region
# Only one path defines region? # Only one path defines region?
self.regions.append({"polygon":Polygon(path), self.regions.append({"polygon": Polygon(path),
"aperture":last_path_aperture}) "aperture": last_path_aperture})
path = [] path = []
continue continue
if gline.find("%ADD") != -1: # aperture definition if gline.find("%ADD") != -1: # aperture definition
self.aperture_parse(gline) # adds element to apertures self.aperture_parse(gline) # adds element to apertures
continue continue
indexstar = gline.find("*") indexstar = gline.find("*")
if gline.find("D") == 0: # Aperture change if gline.find("D") == 0: # Aperture change
current_aperture = gline[1:indexstar] current_aperture = gline[1:indexstar]
continue continue
if gline.find("G54D") == 0: # Aperture change (deprecated) if gline.find("G54D") == 0: # Aperture change (deprecated)
current_aperture = gline[4:indexstar] current_aperture = gline[4:indexstar]
continue continue
if gline.find("%FS") != -1: # Format statement if gline.find("%FS") != -1: # Format statement
indexX = gline.find("X") indexX = gline.find("X")
self.digits = int(gline[indexX + 1]) self.digits = int(gline[indexX + 1])
self.fraction = int(gline[indexX + 2]) self.fraction = int(gline[indexX + 2])
@@ -226,17 +228,17 @@ class Gerber (Geometry):
"aperture":last_path_aperture}) "aperture":last_path_aperture})
def do_flashes(self): def do_flashes(self):
''' """
Creates geometry for Gerber flashes (aperture on a single point). Creates geometry for Gerber flashes (aperture on a single point).
''' """
self.flash_geometry = [] self.flash_geometry = []
for flash in self.flashes: for flash in self.flashes:
aperture = self.apertures[flash['aperture']] aperture = self.apertures[flash['aperture']]
if aperture['type'] == 'C': # Circles if aperture['type'] == 'C': # Circles
circle = Point(flash['loc']).buffer(aperture['size']/2) circle = Point(flash['loc']).buffer(aperture['size']/2)
self.flash_geometry.append(circle) self.flash_geometry.append(circle)
continue continue
if aperture['type'] == 'R': # Rectangles if aperture['type'] == 'R': # Rectangles
loc = flash['loc'] loc = flash['loc']
width = aperture['width'] width = aperture['width']
height = aperture['height'] height = aperture['height']
@@ -260,6 +262,7 @@ class Gerber (Geometry):
[poly['polygon'] for poly in self.regions] + [poly['polygon'] for poly in self.regions] +
self.flash_geometry) self.flash_geometry)
class Excellon(Geometry): class Excellon(Geometry):
def __init__(self): def __init__(self):
Geometry.__init__(self) Geometry.__init__(self)
@@ -275,9 +278,9 @@ class Excellon(Geometry):
self.parse_lines(estr) self.parse_lines(estr)
def parse_lines(self, elines): def parse_lines(self, elines):
''' """
Main Excellon parser. Main Excellon parser.
''' """
current_tool = "" current_tool = ""
for eline in elines: for eline in elines:
@@ -320,7 +323,7 @@ class Excellon(Geometry):
if indexX != -1 and indexY != -1: if indexX != -1 and indexY != -1:
x = float(int(eline[indexX+1:indexY])/10000.0) x = float(int(eline[indexX+1:indexY])/10000.0)
y = float(int(eline[indexY+1:-1])/10000.0) y = float(int(eline[indexY+1:-1])/10000.0)
self.drills.append({'point':Point((x,y)), 'tool':current_tool}) self.drills.append({'point': Point((x, y)), 'tool': current_tool})
continue continue
print "WARNING: Line ignored:", eline print "WARNING: Line ignored:", eline
@@ -335,9 +338,10 @@ class Excellon(Geometry):
self.solid_geometry.append(poly) self.solid_geometry.append(poly)
self.solid_geometry = cascaded_union(self.solid_geometry) self.solid_geometry = cascaded_union(self.solid_geometry)
class CNCjob(Geometry): class CNCjob(Geometry):
def __init__(self, units="in", kind="generic", z_move = 0.1, def __init__(self, units="in", kind="generic", z_move=0.1,
feedrate = 3.0, z_cut = -0.002, tooldia = 0.0): feedrate=3.0, z_cut=-0.002, tooldia=0.0):
# Options # Options
self.kind = kind self.kind = kind
@@ -358,19 +362,17 @@ class CNCjob(Geometry):
# Bounds of geometry given to CNCjob.generate_from_geometry() # Bounds of geometry given to CNCjob.generate_from_geometry()
self.input_geometry_bounds = None self.input_geometry_bounds = None
# Output generated by CNCjob.create_gcode_geometry() # Output generated by CNCjob.create_gcode_geometry()
#self.G_geometry = None #self.G_geometry = None
self.gcode_parsed = None self.gcode_parsed = None
def generate_from_excellon(self, exobj): def generate_from_excellon(self, exobj):
''' """
Generates G-code for drilling from excellon text. Generates G-code for drilling from excellon text.
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 = []
@@ -399,23 +401,23 @@ class CNCjob(Geometry):
gcode += t%point gcode += t%point
gcode += down + up gcode += down + up
gcode += t%(0,0) gcode += t%(0, 0)
gcode += "M05\n" # Spindle stop gcode += "M05\n" # Spindle stop
self.gcode.append(gcode) self.gcode.append(gcode)
def generate_from_geometry(self, geometry, append=True, tooldia=None): def generate_from_geometry(self, geometry, append=True, tooldia=None):
''' """
Generates G-Code for geometry (Shapely collection). Generates G-Code for geometry (Shapely collection).
''' """
if tooldia == None: if tooldia is None:
tooldia = self.tooldia tooldia = self.tooldia
else: else:
self.tooldia = tooldia self.tooldia = tooldia
self.input_geometry_bounds = geometry.bounds self.input_geometry_bounds = geometry.bounds
if append == False: if not append:
self.gcode = "" self.gcode = ""
t = "G0%d X%.4fY%.4f\n" t = "G0%d X%.4fY%.4f\n"
self.gcode = self.unitcode[self.units] + "\n" self.gcode = self.unitcode[self.units] + "\n"
@@ -429,50 +431,50 @@ class CNCjob(Geometry):
for geo in geometry: for geo in geometry:
if type(geo) == Polygon: if type(geo) == Polygon:
path = list(geo.exterior.coords) # Polygon exterior path = list(geo.exterior.coords) # Polygon exterior
self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point
self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting
for pt in path[1:]: for pt in path[1:]:
self.gcode += t%(1, pt[0], pt[1]) # Linear motion to point self.gcode += t%(1, pt[0], pt[1]) # Linear motion to point
self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
for ints in geo.interiors: # Polygon interiors for ints in geo.interiors: # Polygon interiors
path = list(ints.coords) path = list(ints.coords)
self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point
self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting
for pt in path[1:]: for pt in path[1:]:
self.gcode += t%(1, pt[0], pt[1]) # Linear motion to point self.gcode += t%(1, pt[0], pt[1]) # Linear motion to point
self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
continue continue
if type(geo) == LineString or type(geo) == LinearRing: if type(geo) == LineString or type(geo) == LinearRing:
path = list(geo.coords) path = list(geo.coords)
self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point
self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting
for pt in path[1:]: for pt in path[1:]:
self.gcode += t%(1, pt[0], pt[1]) # Linear motion to point self.gcode += t%(1, pt[0], pt[1]) # Linear motion to point
self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
continue continue
if type(geo) == Point: if type(geo) == Point:
path = list(geo.coords) path = list(geo.coords)
self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point
self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting
self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
continue continue
print "WARNING: G-code generation not implemented for %s"%(str(type(geo))) print "WARNING: G-code generation not implemented for %s"%(str(type(geo)))
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 gcode_parse(self): def gcode_parse(self):
steps_per_circ = 20 steps_per_circ = 20
''' """
G-Code parser (from self.gcode). Generates dictionary with G-Code parser (from self.gcode). Generates dictionary with
single-segment LineString's and "kind" indicating cut or travel, single-segment LineString's and "kind" indicating cut or travel,
fast or feedrate speed. fast or feedrate speed.
''' """
geometry = [] geometry = []
# TODO: ???? bring this into the class?? # TODO: ???? bring this into the class??
@@ -513,20 +515,19 @@ class CNCjob(Geometry):
kind[1] = 'S' kind[1] = 'S'
arcdir = [None, None, "cw", "ccw"] arcdir = [None, None, "cw", "ccw"]
if current['G'] in [0,1]: # line if current['G'] in [0, 1]: # line
geometry.append({'geom':LineString([(current['X'],current['Y']), geometry.append({'geom': LineString([(current['X'], current['Y']),
(x,y)]), 'kind':kind}) (x, y)]), 'kind': kind})
if current['G'] in [2,3]: # arc if current['G'] in [2, 3]: # arc
center = [gobj['I'] + current['X'], gobj['J'] + current['Y']] center = [gobj['I'] + current['X'], gobj['J'] + current['Y']]
radius = sqrt(gobj['I']**2 + gobj['J']**2) radius = sqrt(gobj['I']**2 + gobj['J']**2)
start = arctan2( -gobj['J'], -gobj['I']) start = arctan2( -gobj['J'], -gobj['I'])
stop = arctan2(-center[1]+y, -center[0]+x) stop = arctan2(-center[1]+y, -center[0]+x)
geometry.append({'geom':arc(center, radius, start, stop, geometry.append({'geom': arc(center, radius, start, stop,
arcdir[current['G']], arcdir[current['G']],
steps_per_circ), steps_per_circ),
'kind':kind}) 'kind': kind})
# Update current instruction # Update current instruction
for code in gobj: for code in gobj:
current[code] = gobj[code] current[code] = gobj[code]
@@ -536,13 +537,13 @@ class CNCjob(Geometry):
return geometry return geometry
def plot(self, tooldia=None, dpi=75, margin=0.1, def plot(self, tooldia=None, dpi=75, margin=0.1,
color={"T":["#F0E24D", "#B5AB3A"], "C":["#5E6CFF", "#4650BD"]}, color={"T": ["#F0E24D", "#B5AB3A"], "C": ["#5E6CFF", "#4650BD"]},
alpha={"T":0.3, "C":1.0}): alpha={"T": 0.3, "C": 1.0}):
''' """
Creates a Matplotlib figure with a plot of the Creates a Matplotlib figure with a plot of the
G-code job. G-code job.
''' """
if tooldia == None: if tooldia is None:
tooldia = self.tooldia tooldia = self.tooldia
fig = Figure(dpi=dpi) fig = Figure(dpi=dpi)
@@ -571,12 +572,12 @@ class CNCjob(Geometry):
return fig return fig
def plot2(self, axes, tooldia=None, dpi=75, margin=0.1, def plot2(self, axes, tooldia=None, dpi=75, margin=0.1,
color={"T":["#F0E24D", "#B5AB3A"], "C":["#5E6CFF", "#4650BD"]}, color={"T": ["#F0E24D", "#B5AB3A"], "C": ["#5E6CFF", "#4650BD"]},
alpha={"T":0.3, "C":1.0}): alpha={"T": 0.3, "C":1.0}):
''' """
Plots the G-code job onto the given axes. Plots the G-code job onto the given axes.
''' """
if tooldia == None: if tooldia is None:
tooldia = self.tooldia tooldia = self.tooldia
if tooldia == 0: if tooldia == 0:
@@ -598,14 +599,13 @@ class CNCjob(Geometry):
def create_geometry(self): def create_geometry(self):
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 gparse1b(gtext): def gparse1b(gtext):
''' """
gtext is a single string with g-code gtext is a single string with g-code
''' """
gcmds = [] gcmds = []
lines = gtext.split("\n") # TODO: This is probably a lot of work! lines = gtext.split("\n") # TODO: This is probably a lot of work!
for line in lines: for line in lines:
line = line.strip() line = line.strip()
@@ -635,8 +635,8 @@ def gparse1b(gtext):
# 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())
# Separate codes from values # Separate codes from values
cmds = {} cmds = {}
@@ -645,6 +645,7 @@ def gparse1b(gtext):
gcmds.append(cmds) gcmds.append(cmds)
return gcmds return gcmds
def get_bounds(geometry_set): def get_bounds(geometry_set):
xmin = Inf xmin = Inf
ymin = Inf ymin = Inf
@@ -660,12 +661,13 @@ def get_bounds(geometry_set):
return [xmin, ymin, xmax, ymax] return [xmin, ymin, xmax, ymax]
def arc(center, radius, start, stop, direction, steps_per_circ): def arc(center, radius, start, stop, direction, steps_per_circ):
da_sign = {"cw":-1.0, "ccw":1.0} da_sign = {"cw": -1.0, "ccw": 1.0}
points = [] points = []
if direction=="ccw" and stop <= start: if direction == "ccw" and stop <= start:
stop += 2*pi stop += 2*pi
if direction=="cw" and stop >= start: if direction == "cw" and stop >= start:
stop -= 2*pi stop -= 2*pi
angle = abs(stop - start) angle = abs(stop - start)
@@ -677,20 +679,21 @@ def arc(center, radius, start, stop, direction, steps_per_circ):
theta = start + delta_angle*i theta = start + delta_angle*i
points.append([center[0]+radius*cos(theta), center[1]+radius*sin(theta)]) points.append([center[0]+radius*cos(theta), center[1]+radius*sin(theta)])
return LineString(points) return LineString(points)
############### cam.py #################### ############### cam.py ####################
def coord(gstr,digits,fraction): def coord(gstr,digits,fraction):
''' """
Parse Gerber coordinates Parse Gerber coordinates
''' """
global gerbx, gerby global gerbx, gerby
xindex = gstr.find("X") xindex = gstr.find("X")
yindex = gstr.find("Y") yindex = gstr.find("Y")
index = gstr.find("D") index = gstr.find("D")
if (xindex == -1): if xindex == -1:
x = gerbx x = gerbx
y = int(gstr[(yindex+1):index])*(10**(-fraction)) y = int(gstr[(yindex+1):index])*(10**(-fraction))
elif (yindex == -1): elif yindex == -1:
y = gerby y = gerby
x = int(gstr[(xindex+1):index])*(10**(-fraction)) x = int(gstr[(xindex+1):index])*(10**(-fraction))
else: else:
@@ -698,7 +701,5 @@ def coord(gstr,digits,fraction):
y = int(gstr[(yindex+1):index])*(10**(-fraction)) y = int(gstr[(yindex+1):index])*(10**(-fraction))
gerbx = x gerbx = x
gerby = y gerby = y
return [x,y] return [x, y]
################ end of cam.py ############# ################ end of cam.py #############

View File

@@ -86,10 +86,10 @@ def get_entry_float(entry):
def get_entry_eval(entry): def get_entry_eval(entry):
return eval(entry.get_text) return eval(entry.get_text)
getters = {"entry_text":get_entry_text, getters = {"entry_text": get_entry_text,
"entry_int":get_entry_int, "entry_int": get_entry_int,
"entry_float":get_entry_float, "entry_float": get_entry_float,
"entry_eval":get_entry_eval} "entry_eval": get_entry_eval}
setters = {"entry"} setters = {"entry"}
@@ -105,7 +105,7 @@ class App:
self.builder.add_from_file(self.gladefile) self.builder.add_from_file(self.gladefile)
self.window = self.builder.get_object("window1") self.window = self.builder.get_object("window1")
self.window.set_title("Cirkuix") self.window.set_title("Cirkuix")
self.positionLabel = self.builder.get_object("label3") self.position_label = self.builder.get_object("label3")
self.grid = self.builder.get_object("grid1") self.grid = self.builder.get_object("grid1")
self.notebook = self.builder.get_object("notebook1") self.notebook = self.builder.get_object("notebook1")
self.info_label = self.builder.get_object("label_status") self.info_label = self.builder.get_object("label_status")
@@ -248,20 +248,27 @@ class App:
def plot_geometry(self, geometry): def plot_geometry(self, geometry):
for geo in geometry.solid_geometry: for geo in geometry.solid_geometry:
x, y = geo.exterior.coords.xy
self.axes.plot(x, y, 'r-') if type(geo) == Polygon:
for ints in geo.interiors: x, y = geo.exterior.coords.xy
x, y = ints.coords.xy
self.axes.plot(x, y, 'r-') self.axes.plot(x, y, 'r-')
for ints in geo.interiors:
x, y = ints.coords.xy
self.axes.plot(x, y, 'r-')
continue
if type(geo) == LineString or type(geo) == LinearRing:
x, y = geo.coords.xy
self.axes.plot(x, y, 'r-')
continue
self.canvas.queue_draw() self.canvas.queue_draw()
def setup_component_viewer(self): def setup_component_viewer(self):
''' """
List or Tree where whatever has been loaded or created is List or Tree where whatever has been loaded or created is
displayed. displayed.
''' """
self.store = Gtk.ListStore(str) self.store = Gtk.ListStore(str)
self.tree = Gtk.TreeView(self.store) self.tree = Gtk.TreeView(self.store)
select = self.tree.get_selection() select = self.tree.get_selection()
@@ -353,7 +360,59 @@ class App:
######################################## ########################################
## EVENT HANDLERS ## ## EVENT HANDLERS ##
######################################## ########################################
def on_gerber_generate_boundary(self, widget):
margin = self.get_eval("entry_gerber_cutout_margin")
gap_size = self.get_eval("entry_gerber_cutout_gapsize")
gerber = self.stuff[self.selected_item_name]
minx, miny, maxx, maxy = gerber.bounds()
minx -= margin
maxx += margin
miny -= margin
maxy += margin
midx = 0.5 * (minx + maxx)
midy = 0.5 * (miny + maxy)
hgap = 0.5 * gap_size
pts = [[midx-hgap, maxy],
[minx, maxy],
[minx, midy+hgap],
[minx, midy-hgap],
[minx, miny],
[midx-hgap, miny],
[midx+hgap, miny],
[maxx, miny],
[maxx, midy-hgap],
[maxx, midy+hgap],
[maxx, maxy],
[midx+hgap, maxy]]
cases = {"tb": [[pts[0], pts[1], pts[4], pts[5]],
[pts[6], pts[7], pts[10], pts[11]]],
"lr": [[pts[9], pts[10], pts[1], pts[2]],
[pts[3], pts[4], pts[7], pts[8]]],
"4": [[pts[0], pts[1], pts[2]],
[pts[3], pts[4], pts[5]],
[pts[6], pts[7], pts[8]],
[pts[9], pts[10], pts[11]]]}
name = self.selected_item_name + "_cutout"
geometry = CirkuixGeometry(name)
cuts = None
if self.builder.get_object("rb_2tb").get_active():
cuts = cases["tb"]
elif self.builder.get_object("rb_2lr").get_active():
cuts = cases["lr"]
else:
cuts = cases["4"]
geometry.solid_geometry = cascaded_union([LineString(segment) for segment in cuts])
# Add to App and update.
self.stuff[name] = geometry
self.build_list()
def on_eval_update(self, widget): def on_eval_update(self, widget):
"""
Modifies the content of a Gtk.Entry by running
eval() on its contents and puting it back as a
string.
"""
# TODO: error handling here # TODO: error handling here
widget.set_text(str(eval(widget.get_text()))) widget.set_text(str(eval(widget.get_text())))
@@ -384,7 +443,7 @@ class App:
geometry = self.stuff[self.selected_item_name] geometry = self.stuff[self.selected_item_name]
job_name = self.selected_item_name + "_cnc" job_name = self.selected_item_name + "_cnc"
job = CirkuixCNCjob(job_name, z_move = travelz, z_cut = cutz, feedrate = feedrate) job = CirkuixCNCjob(job_name, z_move=travelz, z_cut=cutz, feedrate=feedrate)
job.generate_from_geometry(geometry.solid_geometry) job.generate_from_geometry(geometry.solid_geometry)
job.gcode_parse() job.gcode_parse()
job.create_geometry() job.create_geometry()
@@ -613,11 +672,11 @@ class App:
def on_mouse_move_over_plot(self, event): def on_mouse_move_over_plot(self, event):
try: # May fail in case mouse not within axes try: # May fail in case mouse not within axes
self.positionLabel.set_label("X: %.4f Y: %.4f"%( self.position_label.set_label("X: %.4f Y: %.4f"%(
event.xdata, event.ydata)) event.xdata, event.ydata))
self.mouse = [event.xdata, event.ydata] self.mouse = [event.xdata, event.ydata]
except: except:
self.positionLabel.set_label("") self.position_label.set_label("")
self.mouse = None self.mouse = None
def on_click_over_plot(self, event): def on_click_over_plot(self, event):

View File

@@ -767,9 +767,6 @@
<property name="position">6</property> <property name="position">6</property>
</packing> </packing>
</child> </child>
<child>
<placeholder/>
</child>
<child> <child>
<object class="GtkLabel" id="label13"> <object class="GtkLabel" id="label13">
<property name="visible">True</property> <property name="visible">True</property>
@@ -785,7 +782,7 @@
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">8</property> <property name="position">7</property>
</packing> </packing>
</child> </child>
<child> <child>
@@ -830,7 +827,7 @@
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">9</property> <property name="position">8</property>
</packing> </packing>
</child> </child>
<child> <child>
@@ -842,12 +839,195 @@
<signal name="activate" handler="on_generate_isolation" swapped="no"/> <signal name="activate" handler="on_generate_isolation" swapped="no"/>
<signal name="clicked" handler="on_generate_isolation" swapped="no"/> <signal name="clicked" handler="on_generate_isolation" swapped="no"/>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">9</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">5</property>
<property name="xalign">0</property>
<property name="ypad">3</property>
<property name="label" translatable="yes">Board cutout:</property>
<attributes>
<attribute name="weight" value="semibold"/>
</attributes>
</object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">10</property> <property name="position">10</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkGrid" id="grid4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_spacing">3</property>
<child>
<object class="GtkLabel" id="label27">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">1</property>
<property name="label" translatable="yes">Margin: </property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="entry_gerber_cutout_margin">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="entry_gerber_cutout_gapsize">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label28">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">1</property>
<property name="label" translatable="yes">Gap size: </property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label29">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">1</property>
<property name="label" translatable="yes">Gaps: </property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box10">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkRadioButton" id="rb_2tb">
<property name="label" translatable="yes">2 (T/B)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="margin_right">8</property>
<property name="xalign">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">rb_2lr</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="rb_2lr">
<property name="label" translatable="yes">2 (L/R)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="margin_right">8</property>
<property name="xalign">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="rb_4">
<property name="label" translatable="yes">4</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">rb_2lr</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">11</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button4">
<property name="label" translatable="yes">Generate Geometry</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="activate" handler="on_gerber_generate_boundary" swapped="no"/>
<signal name="clicked" handler="on_gerber_generate_boundary" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">12</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child> <child>
<placeholder/> <placeholder/>
</child> </child>
@@ -997,7 +1177,7 @@
<object class="GtkMenuItem" id="menuitem3"> <object class="GtkMenuItem" id="menuitem3">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="label" translatable="yes">_View</property> <property name="label" translatable="yes">_Tools</property>
<property name="use_underline">True</property> <property name="use_underline">True</property>
</object> </object>
</child> </child>