Kaynağa Gözat

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

Juan Pablo Caram 12 yıl önce
ebeveyn
işleme
80cb2a8de3
3 değiştirilmiş dosya ile 337 ekleme ve 104 silme
  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"
+}