Several minor fixes and features. Plotting, object list, form save, new, delete, etc.

This commit is contained in:
Juan Pablo Caram 2014-01-22 23:55:46 -05:00
parent 3459c55da4
commit 80cb2a8de3
3 changed files with 337 additions and 104 deletions

View File

@ -1,6 +1,6 @@
import threading
from gi.repository import Gtk, Gdk, GLib, GObject
import simplejson as json
from matplotlib.figure import Figure
from numpy import arange, sin, pi
@ -54,10 +54,13 @@ class CirkuixObj:
print "Clearing Axes"
self.axes.cla()
self.axes.set_frame_on(False)
self.axes.set_xticks([])
self.axes.set_yticks([])
self.axes.patch.set_visible(False) # No background
self.axes.set_aspect(1)
return self.axes
#return self.axes
def set_options(self, options):
for name in options:
@ -150,6 +153,24 @@ class CirkuixObj:
# self.axes.cla()
# return
def serialize(self):
"""
Returns a representation of the object as a dictionary so
it can be later exported as JSON. Override this method.
@return: Dictionary representing the object
@rtype: dict
"""
return
def deserialize(self, obj_dict):
"""
Re-builds an object from its serialized version.
@param obj_dict: Dictionary representing a CirkuixObj
@type obj_dict: dict
@return None
"""
return
class CirkuixGerber(CirkuixObj, Gerber):
"""
@ -221,6 +242,12 @@ class CirkuixGerber(CirkuixObj, Gerber):
x, y = ints.coords.xy
self.axes.plot(x, y, linespec)
def serialize(self):
return {
"options": self.options,
"kind": self.kind
}
class CirkuixExcellon(CirkuixObj, Excellon):
"""
@ -408,12 +435,11 @@ class App:
"""
Starts the application and the Gtk.main().
@return: app
@rtype: App
"""
# Needed to interact with the GUI from other threads.
#GLib.threads_init()
GObject.threads_init()
#Gdk.threads_init()
## GUI ##
self.gladefile = "cirkuix.ui"
@ -427,6 +453,7 @@ class App:
self.info_label = self.builder.get_object("label_status")
self.progress_bar = self.builder.get_object("progressbar")
self.progress_bar.set_show_text(True)
self.units_label = self.builder.get_object("label_units")
## Event handling ##
self.builder.connect_signals(self)
@ -449,8 +476,18 @@ class App:
# a key if self.stuff
self.selected_item_name = None
self.defaults = {
"units": "in"
} # Application defaults
self.options = {} # Project options
self.plot_click_subscribers = {}
# Initialization
self.load_defaults()
self.options.update(self.defaults)
self.units_label.set_text("[" + self.options["units"] + "]")
# For debugging only
def someThreadFunc(self):
print "Hello World!"
@ -460,6 +497,7 @@ class App:
########################################
## START ##
########################################
self.window.set_default_size(900, 600)
self.window.show_all()
#Gtk.main()
@ -478,7 +516,7 @@ class App:
#t = arange(0.0,5.0,0.01)
#s = sin(2*pi*t)
#self.axes.plot(t,s)
self.axes.grid()
self.axes.grid(True)
self.figure.patch.set_visible(False)
self.canvas = FigureCanvas(self.figure) # a Gtk.DrawingArea
@ -491,6 +529,7 @@ class App:
self.canvas.set_can_focus(True) # For key press
self.canvas.mpl_connect('key_press_event', self.on_key_over_plot)
#self.canvas.mpl_connect('scroll_event', self.on_scroll_over_plot)
self.canvas.connect("configure-event", self.on_canvas_configure)
self.grid.attach(self.canvas, 0, 0, 600, 400)
@ -499,7 +538,7 @@ class App:
def setup_component_viewer(self):
"""
List or Tree where whatever has been loaded or created is
Sets up list or Tree where whatever has been loaded or created is
displayed.
@return: None
"""
@ -507,6 +546,7 @@ class App:
self.store = Gtk.ListStore(str)
self.tree = Gtk.TreeView(self.store)
#self.list = Gtk.ListBox()
self.tree.connect("row_activated", self.on_row_activated)
self.tree_select = self.tree.get_selection()
self.signal_id = self.tree_select.connect("changed", self.on_tree_selection_changed)
renderer = Gtk.CellRendererText()
@ -531,8 +571,11 @@ class App:
box1 = Gtk.Box(Gtk.Orientation.VERTICAL)
label1 = Gtk.Label("Choose an item from Project")
box1.pack_start(label1, False, False, 1)
box1.pack_start(label1, True, False, 1)
box_selected.pack_start(box1, True, True, 0)
#box_selected.show()
box1.show()
label1.show()
def info(self, text):
"""
@ -578,13 +621,18 @@ class App:
def build_list(self):
"""
Clears and re-populates the list of objects in tcurrently
Clears and re-populates the list of objects in currently
in the project.
@return: None
"""
print "build_list(): clearing"
self.tree_select.unselect_all()
self.store.clear()
print "repopulating...",
for key in self.stuff:
print key,
self.store.append([key])
print
def get_radio_value(self, radio_set):
"""
@ -602,15 +650,29 @@ class App:
Re-generates all plots from all objects.
@return: None
"""
self.clear_plots()
for i in self.stuff:
self.stuff[i].plot(self.figure)
self.on_zoom_fit(None)
self.axes.grid()
self.canvas.queue_draw()
self.set_progress_bar(0.1, "Re-plotting...")
def thread_func(app_obj):
percentage = 0.1
try:
delta = 0.9/len(self.stuff)
except ZeroDivisionError:
GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, ""))
return
for i in self.stuff:
self.stuff[i].plot(self.figure)
percentage += delta
GLib.idle_add(lambda: app_obj.set_progress_bar(percentage, "Re-plotting..."))
self.on_zoom_fit(None)
self.axes.grid(True)
self.canvas.queue_draw()
GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, ""))
t = threading.Thread(target=thread_func, args=(self,))
t.daemon = True
t.start()
def clear_plots(self):
"""
@ -618,9 +680,12 @@ class App:
@return: None
"""
# TODO: Create a setup_axes method that gets called here and in setup_plot?
self.axes.cla()
self.figure.clf()
self.figure.add_axes(self.axes)
self.axes.set_aspect(1)
self.axes.grid(True)
self.canvas.queue_draw()
def get_eval(self, widget_name):
@ -710,11 +775,88 @@ class App:
self.progress_bar.set_fraction(percentage)
return False
def save_project(self):
return
def get_current(self):
"""
Returns the currently selected CirkuixObj in the application.
@return: Currently selected CirkuixObj in the application.
@rtype: CirkuixObj
"""
try:
return self.stuff[self.selected_item_name]
except:
return None
def adjust_axes(self, xmin, ymin, xmax, ymax):
m_x = 15 # pixels
m_y = 25 # pixels
width = xmax-xmin
height = ymax-ymin
r = width/height
Fw, Fh = self.canvas.get_width_height()
Fr = float(Fw)/Fh
x_ratio = float(m_x)/Fw
y_ratio = float(m_y)/Fh
if r > Fr:
ycenter = (ymin+ymax)/2.0
newheight = height*r/Fr
ymin = ycenter-newheight/2.0
ymax = ycenter+newheight/2.0
else:
xcenter = (xmax+ymin)/2.0
newwidth = width*Fr/r
xmin = xcenter-newwidth/2.0
xmax = xcenter+newwidth/2.0
for name in self.stuff:
if self.stuff[name].axes is None:
continue
self.stuff[name].axes.set_xlim((xmin, xmax))
self.stuff[name].axes.set_ylim((ymin, ymax))
self.stuff[name].axes.set_position([x_ratio, y_ratio,
1-2*x_ratio, 1-2*y_ratio])
self.axes.set_xlim((xmin, xmax))
self.axes.set_ylim((ymin, ymax))
self.axes.set_position([x_ratio, y_ratio,
1-2*x_ratio, 1-2*y_ratio])
self.canvas.queue_draw()
def load_defaults(self):
try:
f = open("defaults.json")
options = f.read()
f.close()
except:
self.info("ERROR: Could not load defaults file.")
return
try:
defaults = json.loads(options)
except:
self.info("ERROR: Failed to parse defaults file.")
return
self.defaults.update(defaults)
########################################
## EVENT HANDLERS ##
########################################
def on_canvas_configure(self, widget, event):
print "on_canvas_configure()"
xmin, xmax = self.axes.get_xlim()
ymin, ymax = self.axes.get_ylim()
self.adjust_axes(xmin, ymin, xmax, ymax)
def on_row_activated(self, widget, path, col):
self.notebook.set_current_page(1)
def on_generate_gerber_bounding_box(self, widget):
gerber = self.stuff[self.selected_item_name]
gerber = self.get_current()
gerber.read_form()
name = self.selected_item_name + "_bbox"
@ -734,8 +876,20 @@ class App:
@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)
print "Re-plotting"
self.get_current().read_form()
self.set_progress_bar(0.5, "Plotting...")
#GLib.idle_add(lambda: self.set_progress_bar(0.5, "Plotting..."))
def thread_func(app_obj):
#GLib.idle_add(lambda: app_obj.set_progress_bar(0.5, "Plotting..."))
GLib.idle_add(lambda: app_obj.get_current().plot(app_obj.figure))
GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, ""))
t = threading.Thread(target=thread_func, args=(self,))
t.daemon = True
t.start()
def on_generate_excellon_cncjob(self, widget):
"""
@ -746,13 +900,13 @@ class App:
"""
job_name = self.selected_item_name + "_cnc"
excellon = self.stuff[self.selected_item_name]
excellon = self.get_current()
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]
excellon_ = self.get_current()
assert isinstance(excellon_, CirkuixExcellon)
assert isinstance(job_obj, CirkuixCNCjob)
@ -791,13 +945,13 @@ class App:
@param widget: The widget from which this was called.
@return: None
"""
excellon = self.stuff[self.selected_item_name]
excellon = self.get_current()
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]
obj = self.get_current()
assert isinstance(obj, CirkuixObj)
obj.read_form()
@ -895,7 +1049,7 @@ class App:
# TODO: Object must be updated on form change and the options
# TODO: read from the object.
tooldia = app_obj.get_eval("entry_eval_gerber_isotooldia")
geo_obj.solid_geometry = self.stuff[self.selected_item_name].isolation_geometry(tooldia/2.0)
geo_obj.solid_geometry = self.get_current().isolation_geometry(tooldia/2.0)
# TODO: Do something if this is None. Offer changing name?
self.new_object("geometry", iso_name, iso_init)
@ -953,7 +1107,7 @@ class App:
in a new CirkuixGeometry object.
"""
self.info("Click inside the desired polygon.")
geo = self.stuff[self.selected_item_name]
geo = self.get_current()
geo.read_form()
tooldia = geo.options["painttooldia"]
overlap = geo.options["paintoverlap"]
@ -979,7 +1133,7 @@ class App:
def on_cncjob_exportgcode(self, widget):
def on_success(self, filename):
cncjob = self.stuff[self.selected_item_name]
cncjob = self.get_current()
f = open(filename, 'w')
f.write(cncjob.gcode)
f.close()
@ -987,15 +1141,22 @@ class App:
self.file_chooser_save_action(on_success)
def on_delete(self, widget):
"""
Delete the currently selected CirkuixObj.
@param widget: The widget from which this was called.
@return:
"""
print "on_delete():", self.selected_item_name
# Remove plot
self.figure.delaxes(self.get_current().axes)
self.canvas.queue_draw()
# Remove from dictionary
self.stuff.pop(self.selected_item_name)
#self.tree.get_selection().disconnect(self.signal_id)
# Update UI
self.build_list() # Update the items list
#self.signal_id = self.tree.get_selection().connect(
# "changed", self.on_tree_selection_changed)
self.plot_all()
#self.notebook.set_current_page(1)
def on_replot(self, widget):
self.plot_all()
@ -1023,22 +1184,48 @@ class App:
# Reconnect event listener
self.signal_id = self.tree.get_selection().connect(
"changed", self.on_tree_selection_changed)
def on_tree_selection_changed(self, selection):
"""
Callback for selection change in the project list. This changes
the currently selected CirkuixObj.
@param selection: Selection associated to the project tree or list
@type selection: Gtk.TreeSelection
@return: None
"""
print "on_tree_selection_change(): ",
model, treeiter = selection.get_selected()
if treeiter is not None:
# Save data for previous selection
obj = self.get_current()
if obj is not None:
obj.read_form()
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())
GLib.idle_add(lambda: self.get_current().build_ui())
else:
print "Nothing selected"
self.selected_item_name = None
self.setup_component_editor()
def on_file_new(self, param):
print "File->New not implemented yet."
# Remove everythong from memory
# Clear plot
self.clear_plots()
# Clear object editor
#self.setup_component_editor()
# Clear data
self.stuff = {}
# Clear list
#self.tree_select.unselect_all()
self.build_list()
#print "File->New not implemented yet."
def on_filequit(self, param):
print "quit from menu"
@ -1204,44 +1391,12 @@ class App:
xmin, ymin, xmax, ymax = get_bounds(self.stuff)
width = xmax-xmin
height = ymax-ymin
r = width/height
Fw, Fh = self.canvas.get_width_height()
Fr = float(Fw)/Fh
print "Window aspect ratio:", Fr
print "Data aspect ratio:", r
#self.axes.set_xlim((xmin-0.05*width, xmax+0.05*width))
#self.axes.set_ylim((ymin-0.05*height, ymax+0.05*height))
if r > Fr:
#self.axes.set_xlim((xmin-0.05*width, xmax+0.05*width))
xmin -= 0.05*width
xmax += 0.05*width
ycenter = (ymin+ymax)/2.0
newheight = height*r/Fr
ymin = ycenter-newheight/2.0
ymax = ycenter+newheight/2.0
#self.axes.set_ylim((ycenter-newheight/2.0, ycenter+newheight/2.0))
else:
#self.axes.set_ylim((ymin-0.05*height, ymax+0.05*height))
ymin -= 0.05*height
ymax += 0.05*height
xcenter = (xmax+ymin)/2.0
newwidth = width*Fr/r
xmin = xcenter-newwidth/2.0
xmax = xcenter+newwidth/2.0
#self.axes.set_xlim((xcenter-newwidth/2.0, xcenter+newwidth/2.0))
xmin -= 0.05*width
xmax += 0.05*width
ymin -= 0.05*height
ymax += 0.05*height
self.adjust_axes(xmin, ymin, xmax, ymax)
for name in self.stuff:
self.stuff[name].axes.set_xlim((xmin, xmax))
self.stuff[name].axes.set_ylim((ymin, ymax))
self.axes.set_xlim((xmin, xmax))
self.axes.set_ylim((ymin, ymax))
self.canvas.queue_draw()
return
# def on_scroll_over_plot(self, event):
# print "Scroll"
# center = [event.xdata, event.ydata]

View File

@ -1752,33 +1752,6 @@
<object class="GtkMenu" id="menu2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem6">
<property name="label">gtk-cut</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem7">
<property name="label">gtk-copy</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem8">
<property name="label">gtk-paste</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem9">
<property name="label">gtk-delete</property>
@ -1999,7 +1972,95 @@
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<child>
<placeholder/>
<object class="GtkBox" id="box12">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="label43">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">5</property>
<property name="ypad">3</property>
<property name="label" translatable="yes">APLICATION DEFAULTS</property>
<attributes>
<attribute name="weight" value="semibold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="grid7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
@ -2119,7 +2180,7 @@
</child>
<child>
<object class="GtkLabel" id="label3">
<property name="width_request">120</property>
<property name="width_request">140</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
@ -2132,6 +2193,20 @@
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_units">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">6</property>
<property name="margin_right">6</property>
<property name="label" translatable="yes">[in]</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkProgressBar" id="progressbar">
<property name="width_request">50</property>
@ -2147,7 +2222,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">3</property>
</packing>
</child>
</object>

3
defaults.json Normal file
View File

@ -0,0 +1,3 @@
{
"units": "in"
}