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

107
camlib.py
View File

@@ -7,8 +7,10 @@ from shapely.geometry import MultiPoint, MultiPolygon
from shapely.geometry import box as shply_box
from shapely.ops import cascaded_union
# Used for solid polygons in Matplotlib
from descartes.patch import PolygonPatch
class Geometry:
def __init__(self):
# Units (in or mm)
@@ -18,18 +20,18 @@ class Geometry:
self.solid_geometry = None
def isolation_geometry(self, offset):
'''
"""
Creates contours around geometry at a given
offset distance.
'''
"""
return self.solid_geometry.buffer(offset)
def bounds(self):
'''
"""
Returns coordinates of rectangular bounds
of geometry: (xmin, ymin, xmax, ymax).
'''
if self.solid_geometry == None:
"""
if self.solid_geometry is None:
print "Warning: solid_geometry not computed yet."
return (0, 0, 0, 0)
@@ -39,33 +41,33 @@ class Geometry:
return self.solid_geometry.bounds
def size(self):
'''
"""
Returns (width, height) of rectangular
bounds of geometry.
'''
if self.solid_geometry == None:
"""
if self.solid_geometry is None:
print "Warning: solid_geometry not computed yet."
return 0
bounds = self.bounds()
return (bounds[2]-bounds[0], bounds[3]-bounds[1])
def get_empty_area(self, boundary=None):
'''
"""
Returns the complement of self.solid_geometry within
the given boundary polygon. If not specified, it defaults to
the rectangular bounding box of self.solid_geometry.
'''
if boundary == None:
"""
if boundary is None:
boundary = self.solid_geometry.envelope
return boundary.difference(self.solid_geometry)
def clear_polygon(self, polygon, tooldia, overlap=0.15):
'''
"""
Creates geometry inside a polygon for a tool to cover
the whole area.
'''
"""
poly_cuts = [polygon.buffer(-tooldia/2.0)]
while(1):
while True:
polygon = poly_cuts[-1].buffer(-tooldia*(1-overlap))
if polygon.area > 0:
poly_cuts.append(polygon)
@@ -107,12 +109,12 @@ class Gerber (Geometry):
self.flash_geometry = []
def fix_regions(self):
'''
"""
Overwrites the region polygons with fixed
versions if found to be invalid (according to Shapely).
'''
"""
for region in self.regions:
if region['polygon'].is_valid == False:
if not region['polygon'].is_valid:
region['polygon'] = region['polygon'].buffer(0)
def buffer_paths(self):
@@ -122,10 +124,10 @@ class Gerber (Geometry):
self.buffered_paths.append(path["linestring"].buffer(width/2))
def aperture_parse(self, gline):
'''
"""
Parse gerber aperture definition
into dictionary of apertures.
'''
"""
indexstar = gline.find("*")
indexC = gline.find("C,")
if indexC != -1: # Circle, example: %ADD11C,0.1*%
@@ -153,19 +155,19 @@ class Gerber (Geometry):
return None
def parse_file(self, filename):
'''
"""
Calls Gerber.parse_lines() with array of lines
read from the given file.
'''
"""
gfile = open(filename, 'r')
gstr = gfile.readlines()
gfile.close()
self.parse_lines(gstr)
def parse_lines(self, glines):
'''
"""
Main Gerber parser.
'''
"""
path = [] # Coordinates of the current path
last_path_aperture = None
current_aperture = None
@@ -226,9 +228,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']]
@@ -260,6 +262,7 @@ class Gerber (Geometry):
[poly['polygon'] for poly in self.regions] +
self.flash_geometry)
class Excellon(Geometry):
def __init__(self):
Geometry.__init__(self)
@@ -275,9 +278,9 @@ class Excellon(Geometry):
self.parse_lines(estr)
def parse_lines(self, elines):
'''
"""
Main Excellon parser.
'''
"""
current_tool = ""
for eline in elines:
@@ -335,6 +338,7 @@ class Excellon(Geometry):
self.solid_geometry.append(poly)
self.solid_geometry = cascaded_union(self.solid_geometry)
class CNCjob(Geometry):
def __init__(self, units="in", kind="generic", z_move=0.1,
feedrate=3.0, z_cut=-0.002, tooldia=0.0):
@@ -359,18 +363,16 @@ class CNCjob(Geometry):
# Bounds of geometry given to CNCjob.generate_from_geometry()
self.input_geometry_bounds = None
# Output generated by CNCjob.create_gcode_geometry()
#self.G_geometry = None
self.gcode_parsed = None
def generate_from_excellon(self, exobj):
'''
"""
Generates G-code for drilling from excellon text.
self.gcode becomes a list, each element is a
different job for each tool in the excellon code.
'''
"""
self.kind = "drill"
self.gcode = []
@@ -405,17 +407,17 @@ class CNCjob(Geometry):
self.gcode.append(gcode)
def generate_from_geometry(self, geometry, append=True, tooldia=None):
'''
"""
Generates G-Code for geometry (Shapely collection).
'''
if tooldia == None:
"""
if tooldia is None:
tooldia = self.tooldia
else:
self.tooldia = tooldia
self.input_geometry_bounds = geometry.bounds
if append == False:
if not append:
self.gcode = ""
t = "G0%d X%.4fY%.4f\n"
self.gcode = self.unitcode[self.units] + "\n"
@@ -468,11 +470,11 @@ class CNCjob(Geometry):
def gcode_parse(self):
steps_per_circ = 20
'''
"""
G-Code parser (from self.gcode). Generates dictionary with
single-segment LineString's and "kind" indicating cut or travel,
fast or feedrate speed.
'''
"""
geometry = []
# TODO: ???? bring this into the class??
@@ -526,7 +528,6 @@ class CNCjob(Geometry):
steps_per_circ),
'kind': kind})
# Update current instruction
for code in gobj:
current[code] = gobj[code]
@@ -538,11 +539,11 @@ class CNCjob(Geometry):
def plot(self, tooldia=None, dpi=75, margin=0.1,
color={"T": ["#F0E24D", "#B5AB3A"], "C": ["#5E6CFF", "#4650BD"]},
alpha={"T": 0.3, "C": 1.0}):
'''
"""
Creates a Matplotlib figure with a plot of the
G-code job.
'''
if tooldia == None:
"""
if tooldia is None:
tooldia = self.tooldia
fig = Figure(dpi=dpi)
@@ -573,10 +574,10 @@ class CNCjob(Geometry):
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}):
'''
"""
Plots the G-code job onto the given axes.
'''
if tooldia == None:
"""
if tooldia is None:
tooldia = self.tooldia
if tooldia == 0:
@@ -599,11 +600,10 @@ class CNCjob(Geometry):
self.solid_geometry = cascaded_union([geo['geom'] for geo in self.gcode_parsed])
def gparse1b(gtext):
'''
"""
gtext is a single string with g-code
'''
"""
gcmds = []
lines = gtext.split("\n") # TODO: This is probably a lot of work!
for line in lines:
@@ -645,6 +645,7 @@ def gparse1b(gtext):
gcmds.append(cmds)
return gcmds
def get_bounds(geometry_set):
xmin = Inf
ymin = Inf
@@ -660,6 +661,7 @@ def get_bounds(geometry_set):
return [xmin, ymin, xmax, ymax]
def arc(center, radius, start, stop, direction, steps_per_circ):
da_sign = {"cw": -1.0, "ccw": 1.0}
points = []
@@ -678,19 +680,20 @@ def arc(center, radius, start, stop, direction, steps_per_circ):
points.append([center[0]+radius*cos(theta), center[1]+radius*sin(theta)])
return LineString(points)
############### cam.py ####################
def coord(gstr,digits,fraction):
'''
"""
Parse Gerber coordinates
'''
"""
global gerbx, gerby
xindex = gstr.find("X")
yindex = gstr.find("Y")
index = gstr.find("D")
if (xindex == -1):
if xindex == -1:
x = gerbx
y = int(gstr[(yindex+1):index])*(10**(-fraction))
elif (yindex == -1):
elif yindex == -1:
y = gerby
x = int(gstr[(xindex+1):index])*(10**(-fraction))
else:
@@ -699,6 +702,4 @@ def coord(gstr,digits,fraction):
gerbx = x
gerby = y
return [x, y]
################ end of cam.py #############

View File

@@ -105,7 +105,7 @@ class App:
self.builder.add_from_file(self.gladefile)
self.window = self.builder.get_object("window1")
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.notebook = self.builder.get_object("notebook1")
self.info_label = self.builder.get_object("label_status")
@@ -248,20 +248,27 @@ class App:
def plot_geometry(self, geometry):
for geo in geometry.solid_geometry:
if type(geo) == Polygon:
x, y = geo.exterior.coords.xy
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()
def setup_component_viewer(self):
'''
"""
List or Tree where whatever has been loaded or created is
displayed.
'''
"""
self.store = Gtk.ListStore(str)
self.tree = Gtk.TreeView(self.store)
select = self.tree.get_selection()
@@ -353,7 +360,59 @@ class App:
########################################
## 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):
"""
Modifies the content of a Gtk.Entry by running
eval() on its contents and puting it back as a
string.
"""
# TODO: error handling here
widget.set_text(str(eval(widget.get_text())))
@@ -613,11 +672,11 @@ class App:
def on_mouse_move_over_plot(self, event):
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))
self.mouse = [event.xdata, event.ydata]
except:
self.positionLabel.set_label("")
self.position_label.set_label("")
self.mouse = None
def on_click_over_plot(self, event):

View File

@@ -767,9 +767,6 @@
<property name="position">6</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<object class="GtkLabel" id="label13">
<property name="visible">True</property>
@@ -785,7 +782,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">8</property>
<property name="position">7</property>
</packing>
</child>
<child>
@@ -830,7 +827,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">9</property>
<property name="position">8</property>
</packing>
</child>
<child>
@@ -842,12 +839,195 @@
<signal name="activate" handler="on_generate_isolation" swapped="no"/>
<signal name="clicked" handler="on_generate_isolation" swapped="no"/>
</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>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">10</property>
</packing>
</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>
<placeholder/>
</child>
@@ -997,7 +1177,7 @@
<object class="GtkMenuItem" id="menuitem3">
<property name="visible">True</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>
</object>
</child>