diff --git a/camlib.py b/camlib.py
index 9a4d57b6..a7ed07bc 100644
--- a/camlib.py
+++ b/camlib.py
@@ -129,27 +129,27 @@ class Gerber (Geometry):
into dictionary of apertures.
"""
indexstar = gline.find("*")
- indexC = gline.find("C,")
- if indexC != -1: # Circle, example: %ADD11C,0.1*%
- apid = gline[4:indexC]
+ indexc = gline.find("C,")
+ if indexc != -1: # Circle, example: %ADD11C,0.1*%
+ apid = gline[4:indexc]
self.apertures[apid] = {"type": "C",
- "size": float(gline[indexC+2:indexstar])}
+ "size": float(gline[indexc+2:indexstar])}
return apid
- indexR = gline.find("R,")
- if indexR != -1: # Rectangle, example: %ADD15R,0.05X0.12*%
- apid = gline[4:indexR]
- indexX = gline.find("X")
+ indexr = gline.find("R,")
+ if indexr != -1: # Rectangle, example: %ADD15R,0.05X0.12*%
+ apid = gline[4:indexr]
+ indexx = gline.find("X")
self.apertures[apid] = {"type": "R",
- "width": float(gline[indexR+2:indexX]),
- "height": float(gline[indexX+1:indexstar])}
+ "width": float(gline[indexr+2:indexx]),
+ "height": float(gline[indexx+1:indexstar])}
return apid
- indexO = gline.find("O,")
- if indexO != -1: # Obround
- apid = gline[4:indexO]
- indexX = gline.find("X")
+ indexo = gline.find("O,")
+ if indexo != -1: # Obround
+ apid = gline[4:indexo]
+ indexx = gline.find("X")
self.apertures[apid] = {"type": "O",
- "width": float(gline[indexO+2:indexX]),
- "height": float(gline[indexX+1:indexstar])}
+ "width": float(gline[indexo+2:indexx]),
+ "height": float(gline[indexx+1:indexstar])}
return apid
print "WARNING: Aperture not implemented:", gline
return None
@@ -187,12 +187,12 @@ class Gerber (Geometry):
path = [coord(gline, self.digits, self.fraction)]
continue
- indexD3 = gline.find("D03*")
- if indexD3 > 0: # Flash
+ indexd3 = gline.find("D03*")
+ if indexd3 > 0: # Flash
self.flashes.append({"loc": coord(gline, self.digits, self.fraction),
"aperture": current_aperture})
continue
- if indexD3 == 0: # Flash?
+ if indexd3 == 0: # Flash?
print "WARNING: Uninplemented flash style:", gline
continue
@@ -216,16 +216,16 @@ class Gerber (Geometry):
continue
if gline.find("%FS") != -1: # Format statement
- indexX = gline.find("X")
- self.digits = int(gline[indexX + 1])
- self.fraction = int(gline[indexX + 2])
+ indexx = gline.find("X")
+ self.digits = int(gline[indexx + 1])
+ self.fraction = int(gline[indexx + 2])
continue
print "WARNING: Line ignored:", gline
if len(path) > 1:
# EOF, create shapely LineString if something in path
- self.paths.append({"linestring":LineString(path),
- "aperture":last_path_aperture})
+ self.paths.append({"linestring": LineString(path),
+ "aperture": last_path_aperture})
def do_flashes(self):
"""
@@ -318,11 +318,11 @@ class Excellon(Geometry):
continue
## Drill
- indexX = eline.find("X")
- indexY = eline.find("Y")
- if indexX != -1 and indexY != -1:
- x = float(int(eline[indexX+1:indexY])/10000.0)
- y = float(int(eline[indexY+1:-1])/10000.0)
+ indexx = eline.find("X")
+ indexy = eline.find("Y")
+ if indexx != -1 and indexy != -1:
+ x = float(int(eline[indexx+1:indexy])/10000.0)
+ y = float(int(eline[indexy+1:-1])/10000.0)
self.drills.append({'point': Point((x, y)), 'tool': current_tool})
continue
@@ -342,29 +342,28 @@ class Excellon(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):
-
- # Options
+ """
+
+ @param units:
+ @param kind:
+ @param z_move:
+ @param feedrate:
+ @param z_cut:
+ @param tooldia:
+ """
+ Geometry.__init__(self)
self.kind = kind
self.units = units
self.z_cut = z_cut
self.z_move = z_move
self.feedrate = feedrate
self.tooldia = tooldia
-
- # Constants
self.unitcode = {"in": "G20", "mm": "G21"}
self.pausecode = "G04 P1"
self.feedminutecode = "G94"
self.absolutecode = "G90"
-
- # Input/Output G-Code
self.gcode = ""
-
- # 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):
@@ -377,13 +376,12 @@ class CNCjob(Geometry):
self.gcode = []
t = "G00 X%.4fY%.4f\n"
- down = "G01 Z%.4f\n"%self.z_cut
- 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:
points = []
- gcode = ""
for drill in exobj.drill:
if drill['tool'] == tool:
@@ -392,79 +390,111 @@ class CNCjob(Geometry):
gcode = self.unitcode[self.units] + "\n"
gcode += self.absolutecode + "\n"
gcode += self.feedminutecode + "\n"
- gcode += "F%.2f\n"%self.feedrate
- gcode += "G00 Z%.4f\n"%self.z_move # Move to travel height
- gcode += "M03\n" # Spindle start
+ gcode += "F%.2f\n" % self.feedrate
+ gcode += "G00 Z%.4f\n" % self.z_move # Move to travel height
+ gcode += "M03\n" # Spindle start
gcode += self.pausecode + "\n"
for point in points:
- gcode += t%point
+ gcode += t % point
gcode += down + up
- gcode += t%(0, 0)
+ gcode += t % (0, 0)
gcode += "M05\n" # Spindle stop
self.gcode.append(gcode)
-
+
+ def generate_from_excellon_by_tool(self, exobj, tools="all"):
+ """
+ Creates gcode for this object from an Excellon object
+ for the specified tools.
+ @param exobj: Excellon object to process
+ @type exobj: Excellon
+ @param tools: Comma separated tool names
+ @type: tools: str
+ @return: None
+ """
+ print "Creating CNC Job from Excellon..."
+ if tools == "all":
+ tools = [tool for tool in exobj.tools]
+ else:
+ tools = [x.strip() for x in tools.split(",")]
+ tools = filter(lambda y: y in exobj.tools, tools)
+ print "Tools are:", tools
+
+ points = []
+ for drill in exobj.drills:
+ if drill['tool'] in tools:
+ points.append(drill['point'])
+
+ print "Found %d drills." % len(points)
+ #self.kind = "drill"
+ self.gcode = []
+
+ t = "G00 X%.4fY%.4f\n"
+ down = "G01 Z%.4f\n" % self.z_cut
+ up = "G01 Z%.4f\n" % self.z_move
+
+ gcode = self.unitcode[self.units] + "\n"
+ gcode += self.absolutecode + "\n"
+ gcode += self.feedminutecode + "\n"
+ gcode += "F%.2f\n" % self.feedrate
+ gcode += "G00 Z%.4f\n" % self.z_move # Move to travel height
+ gcode += "M03\n" # Spindle start
+ gcode += self.pausecode + "\n"
+
+ for point in points:
+ x, y = point.coords.xy
+ gcode += t % (x[0], y[0])
+ gcode += down + up
+
+ gcode += t % (0, 0)
+ gcode += "M05\n" # Spindle stop
+
+ self.gcode = gcode
+
def generate_from_geometry(self, geometry, append=True, tooldia=None):
"""
- Generates G-Code for geometry (Shapely collection).
+ Generates G-Code from a Geometry object.
"""
- if tooldia is None:
- tooldia = self.tooldia
- else:
+ if tooldia is not None:
self.tooldia = tooldia
- self.input_geometry_bounds = geometry.bounds
+ self.input_geometry_bounds = geometry.bounds()
if not append:
self.gcode = ""
- t = "G0%d X%.4fY%.4f\n"
+
self.gcode = self.unitcode[self.units] + "\n"
self.gcode += self.absolutecode + "\n"
self.gcode += self.feedminutecode + "\n"
- self.gcode += "F%.2f\n"%self.feedrate
- self.gcode += "G00 Z%.4f\n"%self.z_move # Move to travel height
+ self.gcode += "F%.2f\n" % self.feedrate
+ self.gcode += "G00 Z%.4f\n" % self.z_move # Move to travel height
self.gcode += "M03\n" # Spindle start
self.gcode += self.pausecode + "\n"
- for geo in geometry:
+ for geo in geometry.solid_geometry:
if type(geo) == Polygon:
- path = list(geo.exterior.coords) # Polygon exterior
- 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
- for pt in path[1:]:
- self.gcode += t%(1, pt[0], pt[1]) # Linear motion to point
- self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
- for ints in geo.interiors: # Polygon interiors
- path = list(ints.coords)
- 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
- for pt in path[1:]:
- 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 += self.polygon2gcode(geo)
continue
if type(geo) == LineString or type(geo) == LinearRing:
- path = list(geo.coords)
- 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
- for pt in path[1:]:
- 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 += self.linear2gcode(geo)
continue
if type(geo) == Point:
- path = list(geo.coords)
- 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 += "G00 Z%.4f\n"%self.z_move # Stop cutting
+ self.gcode += self.point2gcode(geo)
continue
-
- print "WARNING: G-code generation not implemented for %s"%(str(type(geo)))
+
+ if type(geo) == MultiPolygon:
+ for poly in geo:
+ self.gcode += self.polygon2gcode(poly)
+ continue
+
+ 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 += "M05\n" # Spindle stop
@@ -475,8 +505,10 @@ class CNCjob(Geometry):
fast or feedrate speed.
"""
+ # TODO: Make this a parameter
steps_per_circ = 20
+ # Results go here
geometry = []
# TODO: ???? bring this into the class??
@@ -523,8 +555,8 @@ class CNCjob(Geometry):
if current['G'] in [2, 3]: # arc
center = [gobj['I'] + current['X'], gobj['J'] + current['Y']]
radius = sqrt(gobj['I']**2 + gobj['J']**2)
- start = arctan2( -gobj['J'], -gobj['I'])
- stop = arctan2(-center[1]+y, -center[0]+x)
+ start = arctan2(-gobj['J'], -gobj['I'])
+ stop = arctan2(-center[1]+y, -center[0]+x)
geometry.append({'geom': arc(center, radius, start, stop,
arcdir[current['G']],
steps_per_circ),
@@ -601,6 +633,49 @@ class CNCjob(Geometry):
def create_geometry(self):
self.solid_geometry = cascaded_union([geo['geom'] for geo in self.gcode_parsed])
+ def polygon2gcode(self, polygon):
+ """
+ Creates G-Code for the exterior and all interior paths
+ of a polygon.
+ @param polygon: A Shapely.Polygon
+ @type polygon: Shapely.Polygon
+ """
+ gcode = ""
+ t = "G0%d X%.4fY%.4f\n"
+ path = list(polygon.exterior.coords) # Polygon exterior
+ gcode += t % (0, path[0][0], path[0][1]) # Move to first point
+ gcode += "G01 Z%.4f\n" % self.z_cut # Start cutting
+ for pt in path[1:]:
+ gcode += t % (1, pt[0], pt[1]) # Linear motion to point
+ gcode += "G00 Z%.4f\n" % self.z_move # Stop cutting
+ for ints in polygon.interiors: # Polygon interiors
+ path = list(ints.coords)
+ gcode += t % (0, path[0][0], path[0][1]) # Move to first point
+ gcode += "G01 Z%.4f\n" % self.z_cut # Start cutting
+ for pt in path[1:]:
+ gcode += t % (1, pt[0], pt[1]) # Linear motion to point
+ gcode += "G00 Z%.4f\n" % self.z_move # Stop cutting
+ return gcode
+
+ def linear2gcode(self, linear):
+ gcode = ""
+ t = "G0%d X%.4fY%.4f\n"
+ path = list(linear.coords)
+ gcode += t%(0, path[0][0], path[0][1]) # Move to first point
+ gcode += "G01 Z%.4f\n" % self.z_cut # Start cutting
+ for pt in path[1:]:
+ gcode += t % (1, pt[0], pt[1]) # Linear motion to point
+ gcode += "G00 Z%.4f\n" % self.z_move # Stop cutting
+ return gcode
+
+ def point2gcode(self, point):
+ gcode = ""
+ t = "G0%d X%.4fY%.4f\n"
+ path = list(point.coords)
+ gcode += t % (0, path[0][0], path[0][1]) # Move to first point
+ gcode += "G01 Z%.4f\n" % self.z_cut # Start cutting
+ gcode += "G00 Z%.4f\n" % self.z_move # Stop cutting
+
def gparse1b(gtext):
"""
@@ -653,7 +728,8 @@ def get_bounds(geometry_set):
ymin = Inf
xmax = -Inf
ymax = -Inf
-
+
+ print "Getting bounds of:", str(geometry_set)
for gs in geometry_set:
gxmin, gymin, gxmax, gymax = geometry_set[gs].bounds()
xmin = min([xmin, gxmin])
@@ -699,7 +775,7 @@ def arc(center, radius, start, stop, direction, steps_per_circ):
return LineString(points)
-def clear_poly(poly, tooldia, overlap = 0.1):
+def clear_poly(poly, tooldia, overlap=0.1):
"""
Creates a list of Shapely geometry objects covering the inside
of a Shapely.Polygon. Use for removing all the copper in a region
diff --git a/cirkuix.py b/cirkuix.py
index 2fa6574d..dbccd001 100644
--- a/cirkuix.py
+++ b/cirkuix.py
@@ -21,15 +21,8 @@ class CirkuixObj:
by the user in their respective forms.
"""
- form_getters = {}
-
- form_setters = {}
-
# Instance of the application to which these are related.
# The app should set this value.
- # TODO: Move the definitions of form_getters and form_setters
- # TODO: to their respective classes and use app to reference
- # TODO: the builder.
app = None
def __init__(self, name):
@@ -41,11 +34,25 @@ class CirkuixObj:
self.kind = None # Override with proper name
def setup_axes(self, figure):
+ """
+ 1) Creates axes if they don't exist. 2) Clears axes. 3) Attaches
+ them to figure if not part of the figure. 4) Sets transparent
+ background. 5) Sets 1:1 scale aspect ratio.
+ @param figure: A Matplotlib.Figure on which to add/configure axes.
+ @type figure: matplotlib.figure.Figure
+ @return: None
+ """
if self.axes is None:
+ print "New axes"
self.axes = figure.add_axes([0.05, 0.05, 0.9, 0.9],
label=self.options["name"])
elif self.axes not in figure.axes:
+ print "Clearing and attaching axes"
+ self.axes.cla()
figure.add_axes(self.axes)
+ else:
+ print "Clearing Axes"
+ self.axes.cla()
self.axes.patch.set_visible(False) # No background
self.axes.set_aspect(1)
@@ -62,7 +69,11 @@ class CirkuixObj:
self.set_form_item(option)
def read_form(self):
- for option in self.form_getters:
+ """
+ Reads form into self.options
+ @rtype : None
+ """
+ for option in self.options:
self.read_form_item(option)
def build_ui(self):
@@ -114,7 +125,7 @@ class CirkuixObj:
fname = fkind + "_" + self.kind + "_" + option
if fkind == 'entry_text':
- self.options[option] = self.app.builder(fname).get_text()
+ self.options[option] = self.app.builder.get_object(fname).get_text()
return
if fkind == 'entry_eval':
self.options[option] = self.app.get_eval(fname)
@@ -127,6 +138,18 @@ class CirkuixObj:
return
print "Unknown kind of form item:", fkind
+ def plot(self, figure):
+ """
+ Extend this method! Sets up axes if needed and
+ clears them. Descendants must do the actual plotting.
+ """
+ # Creates the axes if necessary and sets them up.
+ self.setup_axes(figure)
+
+ # Clear axes.
+ # self.axes.cla()
+ # return
+
class CirkuixGerber(CirkuixObj, Gerber):
"""
@@ -149,9 +172,12 @@ class CirkuixGerber(CirkuixObj, Gerber):
"cutoutmargin": 0.2,
"cutoutgapsize": 0.15,
"gaps": "tb",
- "noncoppermargin": 0.0
+ "noncoppermargin": 0.0,
+ "bboxmargin": 0.0,
+ "bboxrounded": False
})
+ # The 'name' is already in self.form_kinds
self.form_kinds.update({
"plot": "cb",
"mergepolys": "cb",
@@ -161,14 +187,16 @@ class CirkuixGerber(CirkuixObj, Gerber):
"cutoutmargin": "entry_eval",
"cutoutgapsize": "entry_eval",
"gaps": "radio",
- "noncoppermargin": "entry_eval"
+ "noncoppermargin": "entry_eval",
+ "bboxmargin": "entry_eval",
+ "bboxrounded": "cb"
})
self.radios = {"gaps": {"rb_2tb": "tb", "rb_2lr": "lr", "rb_4": "4"}}
self.radios_inv = {"gaps": {"tb": "rb_2tb", "lr": "rb_2lr", "4": "rb_4"}}
def plot(self, figure):
- self.setup_axes(figure)
+ CirkuixObj.plot(self, figure)
self.create_geometry()
@@ -208,15 +236,25 @@ class CirkuixExcellon(CirkuixObj, Excellon):
self.options.update({
"plot": True,
"solid": False,
- "multicolored": False
+ "multicolored": False,
+ "drillz": -0.1,
+ "travelz": 0.1,
+ "feedrate": 5.0,
+ "toolselection": ""
})
self.form_kinds.update({
"plot": "cb",
"solid": "cb",
- "multicolored": "cb"
+ "multicolored": "cb",
+ "drillz": "entry_eval",
+ "travelz": "entry_eval",
+ "feedrate": "entry_eval",
+ "toolselection": "entry_text"
})
+ self.tool_cbs = {}
+
def plot(self, figure):
self.setup_axes(figure)
self.create_geometry()
@@ -229,6 +267,30 @@ class CirkuixExcellon(CirkuixObj, Excellon):
x, y = ints.coords.xy
self.axes.plot(x, y, 'g-')
+ def show_tool_chooser(self):
+ win = Gtk.Window()
+ box = Gtk.Box(spacing=2)
+ box.set_orientation(Gtk.Orientation(1))
+ win.add(box)
+ for tool in self.tools:
+ self.tool_cbs[tool] = Gtk.CheckButton(label=tool+": "+self.tools[tool])
+ box.pack_start(self.tool_cbs[tool], False, False, 1)
+ button = Gtk.Button(label="Accept")
+ box.pack_start(button, False, False, 1)
+ win.show_all()
+
+ def on_accept(widget):
+ win.destroy()
+ tool_list = []
+ for tool in self.tool_cbs:
+ if self.tool_cbs[tool].get_active():
+ tool_list.append(tool)
+ self.options["toolselection"] = ", ".join(tool_list)
+ self.to_form()
+
+ button.connect("activate", on_accept)
+ button.connect("clicked", on_accept)
+
class CirkuixCNCjob(CirkuixObj, CNCjob):
"""
@@ -245,7 +307,7 @@ class CirkuixCNCjob(CirkuixObj, CNCjob):
self.options.update({
"plot": True,
"solid": False,
- "multicolored": "cb",
+ "multicolored": False,
"tooldia": 0.4/25.4 # 0.4mm in inches
})
@@ -259,6 +321,7 @@ class CirkuixCNCjob(CirkuixObj, CNCjob):
def plot(self, figure):
self.setup_axes(figure)
self.plot2(self.axes, tooldia=self.options["tooldia"])
+ app.canvas.queue_draw()
class CirkuixGeometry(CirkuixObj, Geometry):
@@ -279,8 +342,10 @@ class CirkuixGeometry(CirkuixObj, Geometry):
"cutz": -0.002,
"travelz": 0.1,
"feedrate": 5.0,
+ "cnctooldia": 0.4/25.4,
"painttooldia": 0.0625,
- "paintoverlap": 0.15
+ "paintoverlap": 0.15,
+ "paintmargin": 0.01
})
self.form_kinds.update({
@@ -290,13 +355,20 @@ class CirkuixGeometry(CirkuixObj, Geometry):
"cutz": "entry_eval",
"travelz": "entry_eval",
"feedrate": "entry_eval",
+ "cnctooldia": "entry_eval",
"painttooldia": "entry_eval",
- "paintoverlap": "entry_eval"
+ "paintoverlap": "entry_eval",
+ "paintmargin": "entry_eval"
})
def plot(self, figure):
self.setup_axes(figure)
+ try:
+ _ = iter(self.solid_geometry)
+ except TypeError:
+ self.solid_geometry = [self.solid_geometry]
+
for geo in self.solid_geometry:
if type(geo) == Polygon:
@@ -312,6 +384,17 @@ class CirkuixGeometry(CirkuixObj, Geometry):
self.axes.plot(x, y, 'r-')
continue
+ if type(geo) == MultiPolygon:
+ for poly in geo:
+ x, y = poly.exterior.coords.xy
+ self.axes.plot(x, y, 'r-')
+ for ints in poly.interiors:
+ x, y = ints.coords.xy
+ self.axes.plot(x, y, 'r-')
+ continue
+
+ print "WARNING: Did not plot:", str(type(geo))
+
########################################
## App ##
@@ -332,9 +415,7 @@ class App:
GObject.threads_init()
#Gdk.threads_init()
- ########################################
- ## GUI ##
- ########################################
+ ## GUI ##
self.gladefile = "cirkuix.ui"
self.builder = Gtk.Builder()
self.builder.add_from_file(self.gladefile)
@@ -359,9 +440,7 @@ class App:
self.setup_component_viewer()
self.setup_component_editor()
- ########################################
- ## DATA ##
- ########################################
+ ## DATA ##
self.setup_obj_classes()
self.stuff = {} # CirkuixObj's by name
self.mouse = None # Mouse coordinates over plot
@@ -634,12 +713,107 @@ class App:
########################################
## EVENT HANDLERS ##
########################################
+ def on_generate_gerber_bounding_box(self, widget):
+ gerber = self.stuff[self.selected_item_name]
+ gerber.read_form()
+ name = self.selected_item_name + "_bbox"
+
+ def geo_init(geo_obj, app_obj):
+ assert isinstance(geo_obj, CirkuixGeometry)
+ bounding_box = gerber.solid_geometry.envelope.buffer(gerber.options["bboxmargin"])
+ if not gerber.options["bboxrounded"]:
+ bounding_box = bounding_box.envelope
+ geo_obj.solid_geometry = bounding_box
+
+ self.new_object("geometry", name, geo_init)
+
+ def on_update_plot(self, widget):
+ """
+ Callback for button on form for all kinds of objects.
+ Re-plot the current object only.
+ @param widget: The widget from which this was called.
+ @return: None
+ """
+ self.stuff[self.selected_item_name].read_form()
+ self.stuff[self.selected_item_name].plot(self.figure)
+
+ def on_generate_excellon_cncjob(self, widget):
+ """
+ Callback for button active/click on Excellon form to
+ create a CNC Job for the Excellon file.
+ @param widget: The widget from which this was called.
+ @return: None
+ """
+
+ job_name = self.selected_item_name + "_cnc"
+ excellon = self.stuff[self.selected_item_name]
+ assert isinstance(excellon, CirkuixExcellon)
+ excellon.read_form()
+
+ # Object initialization function for app.new_object()
+ def job_init(job_obj, app_obj):
+ excellon_ = self.stuff[self.selected_item_name]
+ assert isinstance(excellon_, CirkuixExcellon)
+ assert isinstance(job_obj, CirkuixCNCjob)
+
+ GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Creating CNC Job..."))
+ job_obj.z_cut = excellon_.options["drillz"]
+ job_obj.z_move = excellon_.options["travelz"]
+ job_obj.feedrate = excellon_.options["feedrate"]
+ # There could be more than one drill size...
+ # job_obj.tooldia = # TODO: duplicate variable!
+ # job_obj.options["tooldia"] =
+ job_obj.generate_from_excellon_by_tool(excellon_, excellon_.options["toolselection"])
+
+ GLib.idle_add(lambda: app_obj.set_progress_bar(0.5, "Parsing G-Code..."))
+ job_obj.gcode_parse()
+
+ GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Creating New Geometry..."))
+ job_obj.create_geometry()
+
+ GLib.idle_add(lambda: app_obj.set_progress_bar(0.8, "Plotting..."))
+
+ # To be run in separate thread
+ def job_thread(app_obj):
+ app_obj.new_object("cncjob", job_name, job_init)
+ GLib.idle_add(lambda: app_obj.set_progress_bar(1.0, "Done!"))
+ GLib.timeout_add_seconds(1, lambda: app_obj.set_progress_bar(0.0, ""))
+
+ # Start the thread
+ t = threading.Thread(target=job_thread, args=(self,))
+ t.daemon = True
+ t.start()
+
+ def on_excellon_tool_choose(self, widget):
+ """
+ Callback for button on Excellon form to open up a window for
+ selecting tools.
+ @param widget: The widget from which this was called.
+ @return: None
+ """
+ excellon = self.stuff[self.selected_item_name]
+ assert isinstance(excellon, CirkuixExcellon)
+ excellon.show_tool_chooser()
+
+ def on_entry_eval_activate(self, widget):
+ self.on_eval_update(widget)
+ obj = self.stuff[self.selected_item_name]
+ assert isinstance(obj, CirkuixObj)
+ obj.read_form()
+
def on_gerber_generate_noncopper(self, widget):
+ """
+ Callback for button on Gerber form to create a geometry object
+ with polygons covering the area without copper or negative of the
+ Gerber.
+ @param widget: The widget from which this was called.
+ @return: None
+ """
name = self.selected_item_name + "_noncopper"
def geo_init(geo_obj, app_obj):
assert isinstance(geo_obj, CirkuixGeometry)
- gerber = app_obj.stuff[self.selected_item_name]
+ gerber = app_obj.stuff[app_obj.selected_item_name]
assert isinstance(gerber, CirkuixGerber)
gerber.read_form()
bounding_box = gerber.solid_geometry.envelope.buffer(gerber.options["noncoppermargin"])
@@ -650,6 +824,12 @@ class App:
self.new_object("geometry", name, geo_init)
def on_gerber_generate_cutout(self, widget):
+ """
+ Callback for button on Gerber form to create geometry with lines
+ for cutting off the board.
+ @param widget: The widget from which this was called.
+ @return: None
+ """
name = self.selected_item_name + "_cutout"
def geo_init(geo_obj, app_obj):
@@ -696,11 +876,18 @@ class App:
Modifies the content of a Gtk.Entry by running
eval() on its contents and puting it back as a
string.
+ @param widget: The widget from which this was called.
+ @return: None
"""
# TODO: error handling here
widget.set_text(str(eval(widget.get_text())))
def on_generate_isolation(self, widget):
+ """
+ Callback for button on Gerber form to create isolation routing geometry.
+ @param widget: The widget from which this was called.
+ @return: None
+ """
print "Generating Isolation Geometry:"
iso_name = self.selected_item_name + "_iso"
@@ -714,45 +901,75 @@ class App:
self.new_object("geometry", iso_name, iso_init)
def on_generate_cncjob(self, widget):
+ """
+ Callback for button on geometry form to generate CNC job.
+ @param widget: The widget from which this was called.
+ @return: None
+ """
print "Generating CNC job"
-
job_name = self.selected_item_name + "_cnc"
+ # Object initialization function for app.new_object()
def job_init(job_obj, app_obj):
- # TODO: Object must be updated on form change and the options
- # TODO: read from the object.
- z_cut = app_obj.get_eval("entry_eval_geometry_cutz")
- z_move = app_obj.get_eval("entry_eval_geometry_travelz")
- feedrate = app_obj.get_eval("entry_eval_geometry_feedrate")
-
- geometry = app_obj.stuff[app_obj.selected_item_name]
assert isinstance(job_obj, CirkuixCNCjob)
- job_obj.z_cut = z_cut
- job_obj.z_move = z_move
- job_obj.feedrate = feedrate
- job_obj.generate_from_geometry(geometry.solid_geometry)
+ geometry = app_obj.stuff[app_obj.selected_item_name]
+ assert isinstance(geometry, CirkuixGeometry)
+ geometry.read_form()
+
+ GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Creating CNC Job..."))
+ job_obj.z_cut = geometry.options["cutz"]
+ job_obj.z_move = geometry.options["travelz"]
+ job_obj.feedrate = geometry.options["feedrate"]
+ job_obj.options["tooldia"] = geometry.options["cnctooldia"]
+
+ GLib.idle_add(lambda: app_obj.set_progress_bar(0.4, "Analyzing Geometry..."))
+ job_obj.generate_from_geometry(geometry)
+
+ GLib.idle_add(lambda: app_obj.set_progress_bar(0.5, "Parsing G-Code..."))
job_obj.gcode_parse()
+
+ GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Creating New Geometry..."))
job_obj.create_geometry()
- self.new_object("cncjob", job_name, job_init)
+ GLib.idle_add(lambda: app_obj.set_progress_bar(0.8, "Plotting..."))
+
+ # To be run in separate thread
+ def job_thread(app_obj):
+ app_obj.new_object("cncjob", job_name, job_init)
+ GLib.idle_add(lambda: app_obj.set_progress_bar(1.0, "Done!"))
+ GLib.timeout_add_seconds(1, lambda: app_obj.set_progress_bar(0.0, ""))
+
+ # Start the thread
+ t = threading.Thread(target=job_thread, args=(self,))
+ t.daemon = True
+ t.start()
def on_generate_paintarea(self, widget):
+ """
+ Callback for button on geometry form.
+ Subscribes to the "Click on plot" event and continues
+ after the click. Finds the polygon containing
+ the clicked point and runs clear_poly() on it, resulting
+ in a new CirkuixGeometry object.
+ """
self.info("Click inside the desired polygon.")
geo = self.stuff[self.selected_item_name]
geo.read_form()
tooldia = geo.options["painttooldia"]
overlap = geo.options["paintoverlap"]
+ # To be called after clicking on the plot.
def doit(event):
self.plot_click_subscribers.pop("generate_paintarea")
self.info("")
point = [event.xdata, event.ydata]
poly = find_polygon(geo.solid_geometry, point)
+ # Initializes the new geometry object
def gen_paintarea(geo_obj, app_obj):
assert isinstance(geo_obj, CirkuixGeometry)
assert isinstance(app_obj, App)
- cp = clear_poly(poly, tooldia, overlap)
+ cp = clear_poly(poly.buffer(-geo.options["paintmargin"]), tooldia, overlap)
geo_obj.solid_geometry = cp
name = self.selected_item_name + "_paint"
@@ -760,12 +977,6 @@ class App:
self.plot_click_subscribers["generate_paintarea"] = doit
- def on_cncjob_tooldia_activate(self, widget):
- job = self.stuff[self.selected_item_name]
- tooldia = self.get_eval("entry_eval_cncjob_tooldia")
- job.tooldia = tooldia
- print "Changing tool diameter to:", tooldia
-
def on_cncjob_exportgcode(self, widget):
def on_success(self, filename):
cncjob = self.stuff[self.selected_item_name]
@@ -840,10 +1051,10 @@ class App:
Gtk.main_quit()
def file_chooser_action(self, on_success):
- '''
+ """
Opens the file chooser and runs on_success on a separate thread
upon completion of valid file choice.
- '''
+ """
dialog = Gtk.FileChooserDialog("Please choose a file", self.window,
Gtk.FileChooserAction.OPEN,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
@@ -979,7 +1190,7 @@ class App:
for subscriber in self.plot_click_subscribers:
self.plot_click_subscribers[subscriber](event)
- except:
+ except Exception, e:
print "Outside plot!"
def on_zoom_in(self, event):
@@ -1045,15 +1256,15 @@ class App:
def on_key_over_plot(self, event):
print 'you pressed', event.key, event.xdata, event.ydata
- if event.key == '1': # 1
+ if event.key == '1': # 1
self.on_zoom_fit(None)
return
- if event.key == '2': # 2
+ if event.key == '2': # 2
self.zoom(1/1.5, self.mouse)
return
- if event.key == '3': # 3
+ if event.key == '3': # 3
self.zoom(1.5, self.mouse)
return
diff --git a/cirkuix.ui b/cirkuix.ui
index c900cf21..08b39ab6 100644
--- a/cirkuix.ui
+++ b/cirkuix.ui
@@ -94,6 +94,7 @@
False
@@ -194,6 +195,21 @@
6
+
+
+
+ False
+ True
+ 7
+
+
@@ -224,12 +240,9 @@
False
True
- 8
+ 9
-
-
-
@@ -375,16 +388,206 @@
-
+
+
+ False
+ True
+ 6
+
-
+
+
+ False
+ True
+ 7
+
-
+
+
+ False
+ True
+ 8
+
-
+
+ Generate
+ True
+ True
+ True
+
+
+
+
+ False
+ True
+ 9
+
@@ -531,6 +734,21 @@
5
+
+
+ Update Plot
+ True
+ True
+ True
+
+
+
+
+ False
+ True
+ 6
+
+
True
@@ -546,7 +764,7 @@
False
True
- 6
+ 7
@@ -561,6 +779,7 @@
True
●
True
+
1
@@ -575,6 +794,7 @@
True
●
True
+
1
@@ -589,6 +809,7 @@
True
●
True
+
1
@@ -639,11 +860,40 @@
1
+
+
+ True
+ False
+ 1
+ Tool diam:
+
+
+ 0
+ 3
+ 1
+ 1
+
+
+
+
+ True
+ True
+ ●
+ True
+
+
+
+ 1
+ 3
+ 1
+ 1
+
+
False
True
- 7
+ 8
@@ -658,7 +908,7 @@
False
True
- 8
+ 9
@@ -676,7 +926,7 @@
False
True
- 9
+ 10
@@ -705,6 +955,7 @@
True
●
True
+
1
@@ -733,6 +984,7 @@
True
●
True
+
1
@@ -741,11 +993,40 @@
1
+
+
+ True
+ False
+ 1
+ Margin:
+
+
+ 0
+ 2
+ 1
+ 1
+
+
+
+
+ True
+ True
+ ●
+ True
+
+
+
+ 1
+ 2
+ 1
+ 1
+
+
False
True
- 10
+ 11
@@ -760,15 +1041,12 @@
False
True
- 11
+ 12
-
-
-
@@ -854,6 +1132,7 @@
True
False
+ 5
0
3
Plot Options:
@@ -929,6 +1208,21 @@
6
+
+
+ Update Plot
+ True
+ True
+ True
+
+
+
+
+ False
+ True
+ 7
+
+
True
@@ -944,7 +1238,7 @@
False
True
- 7
+ 8
@@ -990,7 +1284,7 @@
False
True
- 8
+ 9
@@ -1005,7 +1299,7 @@
False
True
- 9
+ 10
@@ -1023,7 +1317,7 @@
False
True
- 10
+ 11
@@ -1171,7 +1465,7 @@
False
True
- 11
+ 12
@@ -1186,7 +1480,7 @@
False
True
- 12
+ 13
@@ -1204,7 +1498,7 @@
False
True
- 13
+ 14
@@ -1246,7 +1540,7 @@
False
True
- 14
+ 15
@@ -1261,11 +1555,98 @@
False
True
- 15
+ 16
-
+
+ True
+ False
+ 5
+ 0
+ 3
+ Bounding box:
+
+
+
+
+
+ False
+ True
+ 17
+
+
+
+
+ True
+ False
+ 4
+ 4
+ 1
+
+
+ True
+ False
+ 1
+ Boundary margin:
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ True
+ ●
+ 14
+ True
+
+
+
+ False
+ True
+ 1
+
+
+
+
+ False
+ True
+ 18
+
+
+
+
+ Rounded corners
+ True
+ True
+ False
+ 0
+ True
+
+
+ False
+ True
+ 19
+
+
+
+
+ Generate Bounding Box
+ True
+ True
+ True
+
+
+
+
+ False
+ True
+ 20
+