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.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,18 +20,18 @@ 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)
@@ -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,10 +124,10 @@ 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*%
@@ -153,19 +155,19 @@ class Gerber (Geometry):
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
@@ -226,9 +228,9 @@ 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']]
@@ -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:
@@ -335,6 +338,7 @@ 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):
@@ -359,18 +363,16 @@ 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 = []
@@ -405,17 +407,17 @@ class CNCjob(Geometry):
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"
@@ -468,11 +470,11 @@ class CNCjob(Geometry):
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??
@@ -526,7 +528,6 @@ class CNCjob(Geometry):
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]
@@ -538,11 +539,11 @@ class CNCjob(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)
@@ -573,10 +574,10 @@ class CNCjob(Geometry):
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:
@@ -599,11 +600,10 @@ class CNCjob(Geometry):
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:
@@ -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,6 +661,7 @@ 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 = []
@@ -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)]) 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:
@@ -699,6 +702,4 @@ def coord(gstr,digits,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

@@ -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:
if type(geo) == Polygon:
x, y = geo.exterior.coords.xy x, y = geo.exterior.coords.xy
self.axes.plot(x, y, 'r-') self.axes.plot(x, y, 'r-')
for ints in geo.interiors: for ints in geo.interiors:
x, y = ints.coords.xy x, y = ints.coords.xy
self.axes.plot(x, y, 'r-') 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())))
@@ -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>