فهرست منبع

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

Juan Pablo Caram 12 سال پیش
والد
کامیت
80cb2a8de3
3فایلهای تغییر یافته به همراه337 افزوده شده و 104 حذف شده
  1. 229 74
      cirkuix.py
  2. 105 30
      cirkuix.ui
  3. 3 0
      defaults.json

+ 229 - 74
cirkuix.py

@@ -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]

+ 105 - 30
cirkuix.ui

@@ -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 - 0
defaults.json

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