diff --git a/cirkuix.py b/cirkuix.py
index d716559c..66d76a58 100644
--- a/cirkuix.py
+++ b/cirkuix.py
@@ -1,8 +1,7 @@
import threading
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GLib
+from gi.repository import Gtk, Gdk, GLib, GObject
+
from matplotlib.figure import Figure
from numpy import arange, sin, pi
@@ -14,6 +13,12 @@ from camlib import *
class CirkuixObj:
+ """
+ Base type of objects handled in Cirkuix. These become interactive
+ in the GUI, can be plotted, and their options can be modified
+ by the user in their respective forms.
+ """
+
form_getters = {}
form_setters = {}
@@ -55,8 +60,42 @@ class CirkuixObj:
for name in self.form_getters:
self.options[name] = self.form_getters[name]()
+ def build_ui(self, kind):
+ """
+ Sets up the UI/form for this object.
+ @param kind: Kind of object, i.e. 'gerber'
+ @type kind: str
+ @return: None
+ """
+
+ # Where to the UI for this object
+ box_selected = self.app.builder.get_object("box_selected")
+
+ # Remove anything else in the box
+ box_children = box_selected.get_children()
+ for child in box_children:
+ box_selected.remove(child)
+
+ osw = self.app.builder.get_object("offscrwindow_" + kind) # offscreenwindow
+ sw = self.app.builder.get_object("sw_" + kind) # scrollwindows
+ osw.remove(sw) # TODO: Is this needed ?
+ vp = self.app.builder.get_object("vp_" + kind) # Viewport
+ vp.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(1, 1, 1, 1))
+
+ # Put in the UI
+ box_selected.pack_start(sw, True, True, 0)
+
+ entry_name = self.app.builder.get_object("entry_" + kind + "name")
+ entry_name.set_text(self.name)
+ entry_name.connect("activate", self.app.on_activate_name)
+ self.to_form()
+ sw.show()
+
class CirkuixGerber(CirkuixObj, Gerber):
+ """
+ Represents Gerber code.
+ """
def __init__(self, name):
Gerber.__init__(self)
@@ -75,20 +114,10 @@ class CirkuixGerber(CirkuixObj, Gerber):
}
def build_ui(self):
- print "cirkuixgerber.build_ui()"
- osw = self.app.builder.get_object("offscrwindow_gerber")
- #box1 = self.app.builder.get_object("box_gerber")
- #osw.remove(box1)
- sw = self.app.builder.get_object("sw_gerber")
- osw.remove(sw)
- vp = self.app.builder.get_object("vp_gerber")
- vp.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(1, 1, 1, 1))
- self.app.notebook.append_page(sw, Gtk.Label("Selection"))
- entry_name = self.app.builder.get_object("entry_gerbername")
- entry_name.set_text(self.name)
- entry_name.connect("activate", self.app.on_activate_name)
- self.to_form()
- sw.show()
+ """
+ @return: None
+ """
+ CirkuixObj.build_ui(self, "gerber")
def plot(self, figure):
self.setup_axes(figure)
@@ -118,6 +147,9 @@ class CirkuixGerber(CirkuixObj, Gerber):
class CirkuixExcellon(CirkuixObj, Excellon):
+ """
+ Represents Excellon code.
+ """
def __init__(self, name):
Excellon.__init__(self)
@@ -130,20 +162,7 @@ class CirkuixExcellon(CirkuixObj, Excellon):
}
def build_ui(self):
- print "build_excellon_ui()"
- osw = self.app.builder.get_object("offscrwindow_excellon")
- #box1 = self.app.builder.get_object("box_excellon")
- #osw.remove(box1)
- sw = self.app.builder.get_object("sw_excellon")
- osw.remove(sw)
- vp = self.app.builder.get_object("vp_excellon")
- vp.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(1, 1, 1, 1))
- self.app.notebook.append_page(sw, Gtk.Label("Selection"))
- entry_name = self.app.builder.get_object("entry_excellonname")
- entry_name.set_text(self.name)
- entry_name.connect("activate", self.app.on_activate_name)
- self.to_form()
- sw.show()
+ CirkuixObj.build_ui(self, "excellon")
def plot(self, figure):
self.setup_axes(figure)
@@ -159,6 +178,9 @@ class CirkuixExcellon(CirkuixObj, Excellon):
class CirkuixCNCjob(CirkuixObj, CNCjob):
+ """
+ Represents G-Code.
+ """
def __init__(self, name, units="in", kind="generic", z_move=0.1,
feedrate=3.0, z_cut=-0.002, tooldia=0.0):
CNCjob.__init__(self, units=units, kind=kind, z_move=z_move,
@@ -172,20 +194,7 @@ class CirkuixCNCjob(CirkuixObj, CNCjob):
}
def build_ui(self):
- print "build_cncjob_ui()"
- osw = self.app.builder.get_object("offscrwindow_cncjob")
- #box1 = self.app.builder.get_object("box_cncjob")
- #osw.remove(box1)
- sw = self.app.builder.get_object("sw_cncjob")
- osw.remove(sw)
- vp = self.app.builder.get_object("vp_cncjob")
- vp.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(1, 1, 1, 1))
- self.app.notebook.append_page(sw, Gtk.Label("Selection"))
- entry_name = self.app.builder.get_object("entry_cncjobname")
- entry_name.set_text(self.name)
- entry_name.connect("activate", self.app.on_activate_name)
- self.to_form()
- sw.show()
+ CirkuixObj.build_ui(self, "cncjob")
def plot(self, figure):
self.setup_axes(figure)
@@ -193,6 +202,11 @@ class CirkuixCNCjob(CirkuixObj, CNCjob):
class CirkuixGeometry(CirkuixObj, Geometry):
+ """
+ Geometric object not associated with a specific
+ format.
+ """
+
def __init__(self, name):
CirkuixObj.__init__(self, name)
self.options = {"plot": True,
@@ -209,20 +223,7 @@ class CirkuixGeometry(CirkuixObj, Geometry):
}
def build_ui(self):
- print "build_geometry_ui()"
- osw = self.app.builder.get_object("offscrwindow_geometry")
- #box1 = self.app.builder.get_object("box_geometry")
- #osw.remove(box1)
- sw = self.app.builder.get_object("sw_geometry")
- osw.remove(sw)
- vp = self.app.builder.get_object("vp_geometry")
- vp.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(1, 1, 1, 1))
- self.app.notebook.append_page(sw, Gtk.Label("Selection"))
- entry_name = self.app.builder.get_object("entry_geometryname")
- entry_name.set_text(self.name)
- entry_name.connect("activate", self.app.on_activate_name)
- self.to_form()
- sw.show()
+ CirkuixObj.build_ui(self, "geometry")
def plot(self, figure):
self.setup_axes(figure)
@@ -244,9 +245,20 @@ class CirkuixGeometry(CirkuixObj, Geometry):
class App:
+ """
+ The main application class. The constructor starts the GUI.
+ """
+
def __init__(self):
+ """
+ Starts the application and the Gtk.main().
+ @return: app
+ """
+
# Needed to interact with the GUI from other threads.
GLib.threads_init()
+ GObject.threads_init()
+ #Gdk.threads_init()
########################################
## GUI ##
@@ -296,9 +308,17 @@ class App:
## START ##
########################################
self.window.show_all()
- Gtk.main()
+ #Gtk.main()
def setup_plot(self):
+ """
+ Sets up the main plotting area by creating a matplotlib
+ figure in self.canvas, adding axes and configuring them.
+ These axes should not be ploted on and are just there to
+ display the axes ticks and grid.
+ @return: None
+ """
+
self.figure = Figure(dpi=50)
self.axes = self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label="base", alpha=0.0)
self.axes.set_aspect(1)
@@ -398,7 +418,9 @@ class App:
"""
List or Tree where whatever has been loaded or created is
displayed.
+ @return: None
"""
+
self.store = Gtk.ListStore(str)
self.tree = Gtk.TreeView(self.store)
#self.list = Gtk.ListBox()
@@ -407,24 +429,42 @@ class App:
renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn("Title", renderer, text=0)
self.tree.append_column(column)
- self.builder.get_object("notebook1").append_page(self.tree, Gtk.Label("Project"))
+ #self.builder.get_object("notebook1").append_page(self.tree, Gtk.Label("Project"))
+ self.builder.get_object("box_project").pack_start(self.tree, False, False, 1)
def setup_component_editor(self):
+ """
+ Initial configuration of the component editor. Creates
+ a page titled "Selection" on the notebook on the left
+ side of the main window.
+ @return: None
+ """
+
+ box_selected = self.builder.get_object("box_selected")
+
+ # Remove anything else in the box
+ box_children = box_selected.get_children()
+ for child in box_children:
+ box_selected.remove(child)
+
box1 = Gtk.Box(Gtk.Orientation.VERTICAL)
label1 = Gtk.Label("Choose an item from Project")
box1.pack_start(label1, False, False, 1)
- self.builder.get_object("notebook1").append_page(box1, Gtk.Label("Selection"))
+ box_selected.pack_start(box1, True, True, 0)
def info(self, text):
"""
Show text on the status bar.
+ @return: None
"""
+
self.info_label.set_text(text)
def zoom(self, factor, center=None):
"""
Zooms the plot by factor around a given
center point. Takes care of re-drawing.
+ @return: None
"""
xmin, xmax = self.axes.get_xlim()
ymin, ymax = self.axes.get_ylim()
@@ -455,6 +495,11 @@ class App:
self.canvas.queue_draw()
def build_list(self):
+ """
+ Clears and re-populates the list of objects in tcurrently
+ in the project.
+ @return: None
+ """
self.store.clear()
for key in self.stuff:
self.store.append([key])
@@ -463,12 +508,19 @@ class App:
"""
Returns the radio_set[key] if the radiobutton
whose name is key is active.
+ @return: radio_set[key]
"""
+
for name in radio_set:
if self.builder.get_object(name).get_active():
return radio_set[name]
def plot_all(self):
+ """
+ Re-generates all plots from all objects.
+ @return: None
+ """
+
self.clear_plots()
for i in self.stuff:
@@ -479,88 +531,148 @@ class App:
self.canvas.queue_draw()
def clear_plots(self):
+ """
+ Clears self.axes and self.figure.
+ @return: None
+ """
+
self.axes.cla()
self.figure.clf()
self.figure.add_axes(self.axes)
self.canvas.queue_draw()
def get_eval(self, widget_name):
+ """
+ Runs eval() on the on the text entry of name 'widget_name'
+ and returns the results.
+ @param widget_name: Name of Gtk.Entry
+ @return: Depends on contents of the entry text.
+ """
+
value = self.builder.get_object(widget_name).get_text()
return eval(value)
def set_list_selection(self, name):
+ """
+ Marks a given object as selected in the list ob objects
+ in the GUI. This selection will in turn trigger
+ self.on_tree_selection_changed().
+ @param name: Name of the object.
+ @return: None
+ """
+
iter = self.store.get_iter_first()
while iter is not None and self.store[iter][0] != name:
iter = self.store.iter_next(iter)
self.tree_select.unselect_all()
self.tree_select.select_iter(iter)
+ def new_object(self, kind, name, initialize):
+ """
+ Creates a new specalized CirkuixObj and attaches it to the application,
+ this is, updates the GUI accordingly, any other records and plots it.
+ @param kind: Knd of object to create.
+ @param name: Name for the object.
+ @param initilize: Function to run after the
+ object has been created but before attacing it
+ to the application. Takes the new object and the
+ app as parameters.
+ @return: The object requested
+ @rtype : CirkuixObj extended
+ """
+
+ # Check for existing name
+ if name in self.stuff:
+ return None
+
+ # Create object
+ classdict = {
+ "gerber": CirkuixGerber,
+ "excellon": CirkuixExcellon,
+ "cncjob": CirkuixCNCjob,
+ "geometry": CirkuixGeometry
+ }
+ obj = classdict[kind](name)
+
+ # Initialize as per user request
+ initialize(obj, self)
+
+ # Add to our records
+ self.stuff[name] = obj
+
+ # Update GUI list and select it
+ self.store.append([name])
+ #self.build_list()
+ self.set_list_selection(name)
+ GLib.timeout_add(100, lambda: self.notebook.set_current_page(1))
+
+ # Plot
+ obj.plot(self.figure)
+ obj.axes.set_alpha(0.0)
+ self.on_zoom_fit(None)
+
+ return obj
+
+
########################################
## EVENT HANDLERS ##
########################################
def on_gerber_generate_noncopper(self, widget):
- gerber = self.stuff[self.selected_item_name]
- gerber.read_form()
-
name = self.selected_item_name + "_noncopper"
- bounding_box = gerber.solid_geometry.envelope.buffer(gerber.options["noncoppermargin"])
-
- non_copper = bounding_box.difference(gerber.solid_geometry)
-
- geometry = CirkuixGeometry(name)
- geometry.solid_geometry = non_copper
-
- self.stuff[name] = geometry
- self.build_list()
+ def geo_init(geo_obj, app_obj):
+ assert isinstance(geo_obj, CirkuixGeometry)
+ gerber = app_obj.stuff[self.selected_item_name]
+ assert isinstance(gerber, CirkuixGerber)
+ gerber.read_form()
+ bounding_box = gerber.solid_geometry.envelope.buffer(gerber.options["noncoppermargin"])
+ non_copper = bounding_box.difference(gerber.solid_geometry)
+ geo_obj.solid_geometry = non_copper
+ # TODO: Check for None
+ self.new_object("geometry", name, geo_init)
def on_gerber_generate_cutout(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 geo_init(geo_obj, app_obj):
+ # TODO: get from object
+ margin = app_obj.get_eval("entry_gerber_cutout_margin")
+ gap_size = app_obj.get_eval("entry_gerber_cutout_gapsize")
+ gerber = app_obj.stuff[app_obj.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]]]}
+ cuts = cases[app.get_radio_value({"rb_2tb": "tb", "rb_2lr": "lr", "rb_4": "4"})]
+ geo_obj.solid_geometry = cascaded_union([LineString(segment) for segment in cuts])
+
+ # TODO: Check for None
+ self.new_object("geometry", name, geo_init)
def on_eval_update(self, widget):
"""
@@ -573,39 +685,40 @@ class App:
def on_generate_isolation(self, widget):
print "Generating Isolation Geometry:"
- # Get required info
- tooldia = self.builder.get_object("entry_gerberisotooldia").get_text()
- tooldia = eval(tooldia)
- print "tooldia:", tooldia
-
- # Generate
- iso = self.stuff[self.selected_item_name].isolation_geometry(tooldia/2.0)
- # TODO: This will break if there is something with this name already
iso_name = self.selected_item_name + "_iso"
- geo = CirkuixGeometry(iso_name)
- geo.solid_geometry = iso
- # Add to App and update.
- self.stuff[iso_name] = geo
- self.build_list()
+ def iso_init(geo_obj, app_obj):
+ # TODO: Object must be updated on form change and the options
+ # TODO: read from the object.
+ tooldia = app_obj.get_eval("entry_gerberisotooldia")
+ geo_obj.solid_geometry = self.stuff[self.selected_item_name].isolation_geometry(tooldia/2.0)
+
+ # TODO: Do something if this is None. Offer changing name?
+ self.new_object("geometry", iso_name, iso_init)
+
def on_generate_cncjob(self, widget):
print "Generating CNC job"
- # Get required info
- cutz = self.get_eval("entry_geometry_cutz")
- travelz = self.get_eval("entry_geometry_travelz")
- feedrate = self.get_eval("entry_geometry_feedrate")
-
- geometry = self.stuff[self.selected_item_name]
+
job_name = self.selected_item_name + "_cnc"
- job = CirkuixCNCjob(job_name, z_move=travelz, z_cut=cutz, feedrate=feedrate)
- job.generate_from_geometry(geometry.solid_geometry)
- job.gcode_parse()
- job.create_geometry()
-
- # Add to App and update.
- self.stuff[job_name] = job
- self.build_list()
+
+ 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_geometry_cutz")
+ z_move = app_obj.get_eval("entry_geometry_travelz")
+ feedrate = app_obj.get_eval("entry_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)
+ job_obj.gcode_parse()
+ job_obj.create_geometry()
+
+ self.new_object("cncjob", job_name, job_init)
def on_cncjob_tooldia_activate(self, widget):
job = self.stuff[self.selected_item_name]
@@ -626,7 +739,7 @@ class App:
self.stuff.pop(self.selected_item_name)
#self.tree.get_selection().disconnect(self.signal_id)
- self.build_list() # Update the items list
+ self.build_list() # Update the items list
#self.signal_id = self.tree.get_selection().connect(
# "changed", self.on_tree_selection_changed)
@@ -658,27 +771,17 @@ class App:
if treeiter is not None:
print "You selected", model[treeiter][0]
+ self.selected_item_name = model[treeiter][0]
+ #self.stuff[self.selected_item_name].build_ui()
+ GLib.timeout_add(100, lambda: self.stuff[self.selected_item_name].build_ui())
else:
- return # TODO: Clear "Selected" page
-
- self.selected_item_name = model[treeiter][0]
- # Remove the current selection page
- # from the notebook
- # TODO: Assuming it was last page or #2. Find the right page
- self.builder.get_object("notebook1").remove_page(2)
+ print "Nothing selected"
+ self.selected_item_name = None
+ self.setup_component_editor()
- self.stuff[self.selected_item_name].build_ui()
+ def on_file_new(self, param):
+ print "File->New not implemented yet."
- # Determine the kind of item selected
- #kind = self.stuff[model[treeiter][0]].kind
-
- # Build the UI
- # builder = {"gerber": self.build_gerber_ui,
- # "excellon": self.build_excellon_ui,
- # "cncjob": self.build_cncjob_ui,
- # "geometry": self.build_geometry_ui}
- # builder[kind]()
-
def on_filequit(self, param):
print "quit from menu"
self.window.destroy()
@@ -755,14 +858,18 @@ class App:
self.progress_bar.set_text("Done!")
self.progress_bar.set_fraction(1.0)
- self.notebook.set_current_page(1)
+ #self.notebook.set_current_page(0)
self.set_list_selection(name)
+ #self.notebook.set_current_page(1)
+ GLib.timeout_add(100, lambda: self.notebook.set_current_page(1))
def clear_bar(bar):
bar.set_text("")
bar.set_fraction(0.0)
+ return False
- threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
+ #threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
+ GLib.timeout_add_seconds(1, clear_bar, self.progress_bar)
self.file_chooser_action(on_success)
def on_fileopenexcellon(self, param):
@@ -790,10 +897,17 @@ class App:
self.progress_bar.set_text("Done!")
self.progress_bar.set_fraction(1.0)
+ #self.notebook.set_current_page(0)
+ self.set_list_selection(name)
+ #self.notebook.set_current_page(1)
+ GLib.timeout_add(100, lambda: self.notebook.set_current_page(1))
+
def clear_bar(bar):
bar.set_text("")
bar.set_fraction(0.0)
- threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
+ return False
+ #threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
+ GLib.timeout_add_seconds(1, clear_bar, self.progress_bar)
self.file_chooser_action(on_success)
@@ -828,10 +942,16 @@ class App:
self.progress_bar.set_text("Done!")
self.progress_bar.set_fraction(1.0)
+ #self.notebook.set_current_page(0)
+ self.set_list_selection(name)
+ #self.notebook.set_current_page(1)
+
def clear_bar(bar):
bar.set_text("")
bar.set_fraction(0.0)
- threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
+ return False
+ #threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
+ GLib.timeout_add_seconds(1, clear_bar, self.progress_bar)
self.file_chooser_action(on_success)
def on_mouse_move_over_plot(self, event):
@@ -846,9 +966,12 @@ class App:
def on_click_over_plot(self, event):
# For key presses
self.canvas.grab_focus()
-
- print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f'%(
- event.button, event.x, event.y, event.xdata, event.ydata)
+
+ try:
+ print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f'%(
+ event.button, event.x, event.y, event.xdata, event.ydata)
+ except:
+ print "Outside plot!"
def on_zoom_in(self, event):
self.zoom(1.5)
@@ -926,3 +1049,4 @@ class App:
return
app = App()
+Gtk.main()
diff --git a/cirkuix.ui b/cirkuix.ui
index c07774ab..cd2415b3 100644
--- a/cirkuix.ui
+++ b/cirkuix.ui
@@ -1203,6 +1203,7 @@
False
True
True
+
@@ -1448,6 +1449,7 @@
True