Explorar o código

Created class ObjectCollection to manage the list of objects in the program. Converted the program to use it. Not fully functional yet.

Juan Pablo Caram %!s(int64=12) %!d(string=hai) anos
pai
achega
609561f7a3
Modificáronse 5 ficheiros con 743 adicións e 774 borrados
  1. 324 240
      FlatCAM.py
  2. 394 527
      FlatCAM.ui
  3. 2 2
      FlatCAMObj.py
  4. 22 4
      camlib.py
  5. 1 1
      recent.json

+ 324 - 240
FlatCAM.py

@@ -23,14 +23,17 @@ from numpy import arange, sin, pi
 from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas
 #from mpl_toolkits.axes_grid.anchored_artists import AnchoredText
 
-from camlib import *
+
 import sys
 import urllib
 import copy
 import random
 
+########################################
+##      Imports part of FlatCAM       ##
+########################################
+from camlib import *
 from FlatCAMObj import *
-
 from FlatCAMWorker import Worker
 
 ########################################
@@ -41,6 +44,8 @@ class App:
     The main application class. The constructor starts the GUI.
     """
 
+    version_url = "http://caram.cl/flatcam/VERSION"
+
     def __init__(self):
         """
         Starts the application. Takes no parameters.
@@ -78,7 +83,7 @@ class App:
         self.combo_options = self.builder.get_object("combo_options")
         self.combo_options.set_active(1)
 
-        self.setup_project_list()  # The "Project" tab
+        #self.setup_project_list()  # The "Project" tab
         self.setup_component_editor()  # The "Selected" tab
 
         ## Setup the toolbar. Adds buttons.
@@ -95,16 +100,20 @@ class App:
 
         self.setup_tooltips()
 
+        # TODO: Hardcoded list
+        for kind in ['gerber', 'excellon', 'geometry', 'cncjob']:
+            entry_name = self.builder.get_object("entry_text_" + kind + "_name")
+            entry_name.connect("activate", self.on_activate_name)
+
         #### DATA ####
         self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
         self.setup_obj_classes()
-        self.stuff = {}    # FlatCAMObj's by name
         self.mouse = None  # Mouse coordinates over plot
         self.recent = []
-
-        # What is selected by the user. It is
-        # a key if self.stuff
-        self.selected_item_name = None
+        self.collection = ObjectCollection()
+        self.builder.get_object("box_project").pack_start(self.collection.view, False, False, 1)
+        # TODO: Do this different
+        self.collection.view.connect("row_activated", self.on_row_activated)
 
         # Used to inhibit the on_options_update callback when
         # the options are being changed by the program and not the user.
@@ -140,6 +149,7 @@ class App:
                 #     self.radios_inv.update({obj.kind + "_" + option: obj.radios_inv[option]})
 
         ## Event subscriptions ##
+        # TODO: Move to plotcanvas
         self.plot_click_subscribers = {}
         self.plot_mousemove_subscribers = {}
 
@@ -168,11 +178,11 @@ class App:
         self.worker.start()
 
         #### Check for updates ####
+        # Separate thread (Not worker)
         self.version = 4
         t1 = threading.Thread(target=self.versionCheck)
         t1.daemon = True
         t1.start()
-        # self.worker.add_task(self.versionCheck)
 
         #### For debugging only ###
         def somethreadfunc(app_obj):
@@ -245,27 +255,47 @@ class App:
         """
         FlatCAMObj.app = self
 
-    def setup_project_list(self):
-        """
-        Sets up 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.tree_select = self.tree.get_selection()
-        renderer = Gtk.CellRendererText()
-        column = Gtk.TreeViewColumn("Objects", renderer, text=0)
-        self.tree.append_column(column)
-        self.builder.get_object("box_project").pack_start(self.tree, False, False, 1)
-
-        # Double-click or Enter takes you to the object's options
-        self.tree.connect("row_activated", self.on_row_activated)
-
-        # Changes the selected item and populates the object options form
-        self.signal_id = self.tree_select.connect("changed", self.on_tree_selection_changed)
+    # def setup_project_list(self):
+    #     """
+    #     Sets up list or Tree where whatever has been loaded or created is
+    #     displayed.
+    #
+    #     :return: None
+    #     """
+    #
+    #     # Model
+    #     self.store = Gtk.ListStore(GdkPixbuf.Pixbuf, str)
+    #     #self.store = Gtk.ListStore(str, str)
+    #
+    #     # View
+    #     self.tree = Gtk.TreeView(model=self.store)
+    #
+    #     # Renderers
+    #     renderer_pixbuf = Gtk.CellRendererPixbuf()
+    #     column_pixbuf = Gtk.TreeViewColumn("Type", renderer_pixbuf, pixbuf=0)
+    #     # column_pixbuf = Gtk.TreeViewColumn("Type")
+    #     # column_pixbuf.pack_start(renderer_pixbuf, False)
+    #     # column_pixbuf.add_attribute(renderer_pixbuf, "pixbuf", 1)
+    #     self.tree.append_column(column_pixbuf)
+    #
+    #     # renderer1 = Gtk.CellRendererText()
+    #     # column1 = Gtk.TreeViewColumn("Type", renderer1, text=0)
+    #     # self.tree.append_column(column1)
+    #
+    #     renderer = Gtk.CellRendererText()
+    #     column = Gtk.TreeViewColumn("Object name", renderer, text=1)
+    #     self.tree.append_column(column)
+    #
+    #     self.tree_select = self.tree.get_selection()
+    #
+    #
+    #     self.builder.get_object("box_project").pack_start(self.tree, False, False, 1)
+    #
+    #     # Double-click or Enter takes you to the object's options
+    #     self.tree.connect("row_activated", self.on_row_activated)
+    #
+    #     # Changes the selected item and populates the object options form
+    #     self.signal_id = self.tree_select.connect("changed", self.on_tree_selection_changed)
 
     def setup_component_editor(self):
         """
@@ -293,6 +323,7 @@ class App:
 
     def setup_recent_items(self):
 
+        # TODO: Move this to constructor
         icons = {
             "gerber": "share/flatcam_icon16.png",
             "excellon": "share/drill16.png",
@@ -309,13 +340,6 @@ class App:
 
         # Closure needed to create callbacks in a loop.
         # Otherwise late binding occurs.
-        # def make_callback(func, fname):
-        #     def opener(*args):
-        #         t = threading.Thread(target=lambda: func(fname))
-        #         t.daemon = True
-        #         t.start()
-        #     return opener
-
         def make_callback(func, fname):
             def opener(*args):
                 self.worker.add_task(func, [fname])
@@ -362,21 +386,31 @@ class App:
         """
         self.info_label.set_text(text)
 
-    def build_list(self):
-        """
-        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 build_list(self):
+    #     """
+    #     Clears and re-populates the list of objects in currently
+    #     in the project.
+    #
+    #     :return: None
+    #     """
+    #     icons = {
+    #         "gerber": "share/flatcam_icon16.png",
+    #         "excellon": "share/drill16.png",
+    #         "cncjob": "share/cnc16.png",
+    #         "geometry": "share/geometry16.png"
+    #     }
+    #
+    #
+    #     print "build_list(): clearing"
+    #     self.tree_select.unselect_all()
+    #     self.store.clear()
+    #     print "repopulating...",
+    #     for key in self.stuff:
+    #         print key,
+    #         obj = self.stuff[key]
+    #         icon = GdkPixbuf.Pixbuf.new_from_file(icons[obj.kind])
+    #         self.store.append([icon, key])
+    #     print
 
     def get_radio_value(self, radio_set):
         """
@@ -401,26 +435,24 @@ class App:
         self.plotcanvas.clear()
         self.set_progress_bar(0.1, "Re-plotting...")
 
-        def thread_func(app_obj):
+        def worker_task(app_obj):
             percentage = 0.1
             try:
-                delta = 0.9 / len(self.stuff)
+                delta = 0.9 / len(self.collection.get_list())
             except ZeroDivisionError:
                 GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, ""))
                 return
-            for i in self.stuff:
-                self.stuff[i].plot()
+            for obj in self.collection.get_list():
+                obj.plot()
                 percentage += delta
                 GLib.idle_add(lambda: app_obj.set_progress_bar(percentage, "Re-plotting..."))
 
             GLib.idle_add(app_obj.plotcanvas.auto_adjust_axes)
             GLib.idle_add(lambda: self.on_zoom_fit(None))
-            GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, ""))
+            GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, "Idle"))
 
-        # t = threading.Thread(target=thread_func, args=(self,))
-        # t.daemon = True
-        # t.start()
-        self.worker.add_task(thread_func, [self])
+        # Send to worker
+        self.worker.add_task(worker_task, [self])
 
     def get_eval(self, widget_name):
         """
@@ -442,26 +474,26 @@ class App:
             self.info("Could not evaluate: " + value)
             return None
 
-    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.
-        :type name: str
-        :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)
-
-        # Need to return False such that GLib.idle_add
-        # or .timeout_add do not repeat.
-        return False
+    # 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.
+    #     :type name: str
+    #     :return: None
+    #     """
+    #
+    #     iter = self.store.get_iter_first()
+    #     while iter is not None and self.store[iter][1] != name:
+    #         iter = self.store.iter_next(iter)
+    #     self.tree_select.unselect_all()
+    #     self.tree_select.select_iter(iter)
+    #
+    #     # Need to return False such that GLib.idle_add
+    #     # or .timeout_add do not repeat.
+    #     return False
 
     def new_object(self, kind, name, initialize):
         """
@@ -482,7 +514,7 @@ class App:
         """
 
         ### Check for existing name
-        if name in self.stuff:
+        if name in self.collection.get_names():
             ## Create a new name
             # Ends with number?
             match = re.search(r'(.*[^\d])?(\d+)$', name)
@@ -503,12 +535,6 @@ class App:
         obj = classdict[kind](name)
         obj.units = self.options["units"]  # TODO: The constructor should look at defaults.
 
-        # Initialize as per user request
-        # User must take care to implement initialize
-        # in a thread-safe way as is is likely that we
-        # have been invoked in a separate thread.
-        #initialize(obj, self)
-
         # Set default options from self.options
         for option in self.options:
             if option.find(kind + "_") == 0:
@@ -527,14 +553,10 @@ class App:
             obj.convert_units(self.options["units"])
 
         # Add to our records
-        self.stuff[name] = obj
-
-        # Update GUI list and select it (Thread-safe?)
-        self.store.append([name])
-        #self.build_list()
-        GLib.idle_add(lambda: self.set_list_selection(name))
-        # TODO: Gtk.notebook.set_current_page is not known to
-        # TODO: return False. Fix this??
+        # TODO: Perhaps make collection thread safe instead?
+        GLib.idle_add(lambda: self.collection.append(obj, active=True))
+
+        # Show object details now.
         GLib.timeout_add(100, lambda: self.notebook.set_current_page(1))
 
         # Plot
@@ -560,22 +582,22 @@ class App:
         self.progress_bar.set_fraction(percentage)
         return False
 
-    def get_current(self):
-        """
-        Returns the currently selected FlatCAMObj in the application.
-
-        :return: Currently selected FlatCAMObj in the application.
-        :rtype: FlatCAMObj or None
-        """
-
-        # TODO: Could possibly read the form into the object here.
-        # But there are some cases when the form for the object
-        # is not up yet. See on_tree_selection_changed.
-
-        try:
-            return self.stuff[self.selected_item_name]
-        except:
-            return None
+    # def get_current(self):
+    #     """
+    #     Returns the currently selected FlatCAMObj in the application.
+    #
+    #     :return: Currently selected FlatCAMObj in the application.
+    #     :rtype: FlatCAMObj or None
+    #     """
+    #
+    #     # TODO: Could possibly read the form into the object here.
+    #     # But there are some cases when the form for the object
+    #     # is not up yet. See on_tree_selection_changed.
+    #
+    #     try:
+    #         return self.stuff[self.selected_item_name]
+    #     except:
+    #         return None
 
     def load_defaults(self):
         """
@@ -712,11 +734,11 @@ class App:
 
         # Capture the latest changes
         try:
-            self.get_current().read_form()
+            self.collection.get_active().read_form()
         except:
             pass
 
-        d = {"objs": [self.stuff[o].to_dict() for o in self.stuff],
+        d = {"objs": [obj.to_dict() for obj in self.collection.get_list()],
              "options": self.options}
 
         try:
@@ -746,15 +768,12 @@ class App:
         try:
             f = open(filename, 'r')
         except:
-            #print "WARNING: Failed to open project file:", filename
             self.info("ERROR: Failed to open project file: %s" % filename)
             return
 
         try:
             d = json.load(f, object_hook=dict2obj)
         except:
-            #print sys.exc_info()
-            #print "WARNING: Failed to parse project file:", filename
             self.info("ERROR: Failed to parse project file: %s" % filename)
             f.close()
             return
@@ -790,8 +809,8 @@ class App:
             combo = self.builder.get_object(combo)
 
         combo.remove_all()
-        for obj in self.stuff:
-            combo.append_text(obj)
+        for name in self.collection.get_names():
+            combo.append_text(name)
 
     def versionCheck(self, *args):
         """
@@ -803,7 +822,7 @@ class App:
         """
 
         try:
-            f = urllib.urlopen("http://caram.cl/flatcam/VERSION")  # TODO: Hardcoded.
+            f = urllib.urlopen(App.version_url)
         except:
             GLib.idle_add(lambda: self.info("ERROR trying to check for latest version."))
             return
@@ -868,52 +887,49 @@ class App:
 
         self.set_progress_bar(0.1, "Re-plotting...")
 
-        def thread_func(app_obj):
+        def worker_task(app_obj):
             percentage = 0.1
             try:
-                delta = 0.9 / len(self.stuff)
+                delta = 0.9 / len(self.collection.get_list())
             except ZeroDivisionError:
                 GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, ""))
                 return
-            for i in self.stuff:
-                if i != app_obj.selected_item_name or not except_current:
-                    self.stuff[i].options['plot'] = False
-                    self.stuff[i].plot()
+            for obj in self.collection.get_list():
+                #if i != app_obj.selected_item_name or not except_current:
+                if obj != self.collection.get_active() or not except_current:
+                    obj.options['plot'] = False
+                    obj.plot()
                 percentage += delta
                 GLib.idle_add(lambda: app_obj.set_progress_bar(percentage, "Re-plotting..."))
 
             GLib.idle_add(app_obj.plotcanvas.auto_adjust_axes)
             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()
-        self.worker.add_task(thread_func, [self])
+        # Send to worker
+        self.worker.add_task(worker_task, [self])
 
     def enable_all_plots(self, *args):
         self.plotcanvas.clear()
         self.set_progress_bar(0.1, "Re-plotting...")
 
-        def thread_func(app_obj):
+        def worker_task(app_obj):
             percentage = 0.1
             try:
-                delta = 0.9 / len(self.stuff)
+                delta = 0.9 / len(self.collection.get_list())
             except ZeroDivisionError:
                 GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, ""))
                 return
-            for i in self.stuff:
-                self.stuff[i].options['plot'] = True
-                self.stuff[i].plot()
+            for obj in self.collection.get_list():
+                obj.options['plot'] = True
+                obj.plot()
                 percentage += delta
                 GLib.idle_add(lambda: app_obj.set_progress_bar(percentage, "Re-plotting..."))
 
             GLib.idle_add(app_obj.plotcanvas.auto_adjust_axes)
             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()
-        self.worker.add_task(thread_func, [self])
+        # Send to worker
+        self.worker.add_task(worker_task, [self])
 
     def register_recent(self, kind, filename):
         record = {'kind': kind, 'filename': filename}
@@ -960,7 +976,7 @@ class App:
         :return: None
         """
 
-        obj = self.get_current()
+        obj = self.collection.get_active()
         obj.read_form()
         assert isinstance(obj, FlatCAMObj)
         try:
@@ -981,8 +997,10 @@ class App:
         :return: None
         """
 
-        self.get_current().read_form()
-        self.get_current().plot()
+        # self.get_current().read_form()
+        # self.get_current().plot()
+        self.collection.get_active().read_form()
+        self.collection.get_active().plot()
 
     def on_about(self, widget):
         """
@@ -1007,12 +1025,8 @@ class App:
         # TODO: Move (some of) this to camlib!
 
         # Object to mirror
-        try:
-            obj_name = self.builder.get_object("comboboxtext_bottomlayer").get_active_text()
-            fcobj = self.stuff[obj_name]
-        except KeyError:
-            self.info("WARNING: Cannot mirror that object.")
-            return
+        obj_name = self.builder.get_object("comboboxtext_bottomlayer").get_active_text()
+        fcobj = self.collection.get_by_name(obj_name)
 
         # For now, lets limit to Gerbers and Excellons.
         # assert isinstance(gerb, FlatCAMGerber)
@@ -1030,23 +1044,13 @@ class App:
             px, py = eval(self.point_entry.get_text())
         else:  # The axis is the line dividing the box in the middle
             name = self.box_combo.get_active_text()
-            bb_obj = self.stuff[name]
+            bb_obj = self.collection.get_by_name(name)
             xmin, ymin, xmax, ymax = bb_obj.bounds()
             px = 0.5*(xmin+xmax)
             py = 0.5*(ymin+ymax)
 
-        # Do the mirroring
-        # xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
-        # mirrored = affinity.scale(fcobj.solid_geometry, xscale, yscale, origin=(px, py))
-        #
-        # def obj_init(obj_inst, app_inst):
-        #     obj_inst.solid_geometry = mirrored
-        #
-        # self.new_object("gerber", fcobj.options["name"] + "_mirror", obj_init)
-
         fcobj.mirror(axis, [px, py])
         fcobj.plot()
-        #self.on_update_plot(None)
 
     def on_create_aligndrill(self, widget):
         """
@@ -1067,7 +1071,7 @@ class App:
             px, py = eval(self.point_entry.get_text())
         else:
             name = self.box_combo.get_active_text()
-            bb_obj = self.stuff[name]
+            bb_obj = self.collection.get_by_name(name)
             xmin, ymin, xmax, ymax = bb_obj.bounds()
             px = 0.5*(xmin+xmax)
             py = 0.5*(ymin+ymax)
@@ -1232,11 +1236,10 @@ class App:
             self.read_form()
             scale_options(factor)
             self.options2form()
-            for obj in self.stuff:
+            for obj in self.collection.get_list():
                 units = self.get_radio_value({"rb_mm": "MM", "rb_inch": "IN"})
-                #print "Converting ", obj, " to ", units
-                self.stuff[obj].convert_units(units)
-            current = self.get_current()
+                obj.convert_units(units)
+            current = self.collection.get_active()
             if current is not None:
                 current.to_form()
             self.plot_all()
@@ -1355,7 +1358,7 @@ class App:
         :return: None
         """
 
-        obj = self.get_current()
+        obj = self.collection.get_active()
         if obj is None:
             self.info("WARNING: No object selected.")
             return
@@ -1374,7 +1377,7 @@ class App:
         :return: None
         """
 
-        obj = self.get_current()
+        obj = self.collection.get_active()
         if obj is None:
             self.info("WARNING: No object selected.")
             return
@@ -1393,7 +1396,7 @@ class App:
         :param param: Ignored.
         :return: None
         """
-        obj = self.get_current()
+        obj = self.collection.get_active()
         if obj is None:
             self.info("WARNING: No object selected.")
             return
@@ -1413,7 +1416,7 @@ class App:
         :return: None
         """
 
-        obj = self.get_current()
+        obj = self.collection.get_active()
         if obj is None:
             self.info("WARNING: No object selected.")
             return
@@ -1501,7 +1504,7 @@ class App:
         :return: None
         """
 
-        obj = self.get_current()
+        obj = self.collection.get_active()
         factor = self.get_eval("entry_eval_" + obj.kind + "_scalefactor")
         obj.scale(factor)
         obj.to_form()
@@ -1542,7 +1545,7 @@ class App:
         :return: None
         """
         # TODO: Use Gerber.get_bounding_box(...)
-        gerber = self.get_current()
+        gerber = self.collection.get_active()
         gerber.read_form()
         name = gerber.options["name"] + "_bbox"
 
@@ -1565,7 +1568,7 @@ class App:
         :return: None
         """
 
-        obj = self.get_current()
+        obj = self.collection.get_active()
         obj.read_form()
 
         self.set_progress_bar(0.5, "Plotting...")
@@ -1589,7 +1592,7 @@ class App:
         :return: None
         """
 
-        excellon = self.get_current()
+        excellon = self.collection.get_active()
         excellon.read_form()
         job_name = excellon.options["name"] + "_cnc"
 
@@ -1636,7 +1639,7 @@ class App:
         :param widget: The widget from which this was called.
         :return: None
         """
-        excellon = self.get_current()
+        excellon = self.collection.get_active()
         assert isinstance(excellon, FlatCAMExcellon)
         excellon.show_tool_chooser()
 
@@ -1650,7 +1653,7 @@ class App:
         :return:
         """
         self.on_eval_update(widget)
-        obj = self.get_current()
+        obj = self.collection.get_active()
         assert isinstance(obj, FlatCAMObj)
         obj.read_form()
 
@@ -1664,7 +1667,7 @@ class App:
         :return: None
         """
 
-        gerb = self.get_current()
+        gerb = self.collection.get_active()
         gerb.read_form()
         name = gerb.options["name"] + "_noncopper"
 
@@ -1688,7 +1691,7 @@ class App:
         :return: None
         """
 
-        gerb = self.get_current()
+        gerb = self.collection.get_active()
         gerb.read_form()
         name = gerb.options["name"] + "_cutout"
 
@@ -1749,7 +1752,7 @@ class App:
         :return: None
         """
 
-        gerb = self.get_current()
+        gerb = self.collection.get_active()
         gerb.read_form()
         dia = gerb.options["isotooldia"]
         passes = int(gerb.options["isopasses"])
@@ -1779,7 +1782,7 @@ class App:
         :return: None
         """
 
-        source_geo = self.get_current()
+        source_geo = self.collection.get_active()
         source_geo.read_form()
         job_name = source_geo.options["name"] + "_cnc"
 
@@ -1834,7 +1837,7 @@ class App:
         """
 
         self.info("Click inside the desired polygon.")
-        geo = self.get_current()
+        geo = self.collection.get_active()
         geo.read_form()
         assert isinstance(geo, FlatCAMGeometry)
         tooldia = geo.options["painttooldia"]
@@ -1868,7 +1871,7 @@ class App:
         :return: None
         """
         def on_success(app_obj, filename):
-            cncjob = app_obj.get_current()
+            cncjob = app_obj.collection.get_active()
             f = open(filename, 'w')
             f.write(cncjob.gcode)
             f.close()
@@ -1885,17 +1888,17 @@ class App:
         """
 
         # Keep this for later
-        name = copy.copy(self.selected_item_name)
+        name = copy.copy(self.collection.get_active().options["name"])
 
         # Remove plot
-        self.plotcanvas.figure.delaxes(self.get_current().axes)
+        self.plotcanvas.figure.delaxes(self.collection.get_active().axes)
         self.plotcanvas.auto_adjust_axes()
 
-        # Remove from dictionary
-        self.stuff.pop(self.selected_item_name)
+        # Clear form
+        self.setup_component_editor()
 
-        # Update UI
-        self.build_list()  # Update the items list
+        # Remove from dictionary
+        self.collection.delete_active()
 
         self.info("Object deleted: %s" % name)
 
@@ -1907,7 +1910,7 @@ class App:
         :return: None
         """
 
-        self.get_current().read_form()
+        self.collection.get_active().read_form()
 
         self.plot_all()
 
@@ -1929,49 +1932,10 @@ class App:
         :return: None
         """
 
-        # Disconnect event listener
-        self.tree.get_selection().disconnect(self.signal_id)
-
-        new_name = entry.get_text()  # Get from form
-        self.stuff[new_name] = self.stuff.pop(self.selected_item_name)  # Update dictionary
-        self.stuff[new_name].options["name"] = new_name  # update object
-        self.info('Name change: ' + self.selected_item_name + " to " + new_name)
-
-        self.selected_item_name = new_name  # Update selection name
-
-        self.build_list()  # Update the items list
-
-        # 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 FlatCAMObj.
-
-        :param selection: Selection associated to the project tree or list
-        :type selection: Gtk.TreeSelection
-        :return: None
-        """
-        print "DEBUG: 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 "DEBUG: You selected", model[treeiter][0]
-            self.selected_item_name = model[treeiter][0]
-            obj_new = self.get_current()
-            if obj_new is not None:
-                GLib.idle_add(lambda: obj_new.build_ui())
-        else:
-            print "DEBUG: Nothing selected"
-            self.selected_item_name = None
-            self.setup_component_editor()
+        old_name = copy.copy(self.collection.get_active().options["name"])
+        new_name = entry.get_text()
+        self.collection.change_name(old_name, new_name)
+        self.info("Name changed from %s to %s" % (old_name, new_name))
 
     def on_file_new(self, param):
         """
@@ -1981,19 +1945,18 @@ class App:
         :param param: Whatever is passed by the event. Ignore.
         :return: None
         """
-        # Remove everythong from memory
+        # Remove everything from memory
         # Clear plot
         self.plotcanvas.clear()
 
-        # Clear object editor
-        #self.setup_component_editor()
+        # Delete data
+        self.collection.delete_all()
 
-        # Clear data
-        self.stuff = {}
+        # Clear object editor
+        self.setup_component_editor()
 
         # Clear list
-        #self.tree_select.unselect_all()
-        self.build_list()
+        self.collection.build_list()
 
         # Clear project filename
         self.project_filename = None
@@ -2339,7 +2302,7 @@ class App:
         :param event: Ignored.
         :return: None
         """
-        xmin, ymin, xmax, ymax = get_bounds(self.stuff)
+        xmin, ymin, xmax, ymax = self.collection.get_bounds()
         width = xmax - xmin
         height = ymax - ymin
         xmin -= 0.05 * width
@@ -2804,11 +2767,79 @@ class PlotCanvas:
 
 
 class ObjectCollection:
+
+    classdict = {
+        "gerber": FlatCAMGerber,
+        "excellon": FlatCAMExcellon,
+        "cncjob": FlatCAMCNCjob,
+        "geometry": FlatCAMGeometry
+    }
+
+    icons = {
+        "gerber": "share/flatcam_icon16.png",
+        "excellon": "share/drill16.png",
+        "cncjob": "share/cnc16.png",
+        "geometry": "share/geometry16.png"
+    }
+
     def __init__(self):
+
+        ### Data
+        # List of FLatCAMObject's
         self.collection = []
         self.active = None
 
+        ### GUI List components
+        ## Model
+        self.store = Gtk.ListStore(GdkPixbuf.Pixbuf, str)
+
+        ## View
+        self.view = Gtk.TreeView(model=self.store)
+        self.view.connect("row_activated", self.on_row_activated)
+        self.tree_selection = self.view.get_selection()
+        self.change_subscription = self.tree_selection.connect("changed", self.on_list_selection_change)
+
+        # Renderers
+        renderer_pixbuf = Gtk.CellRendererPixbuf()
+        column_pixbuf = Gtk.TreeViewColumn("Type", renderer_pixbuf, pixbuf=0)
+        self.view.append_column(column_pixbuf)
+
+        renderer_text = Gtk.CellRendererText()
+        column_text = Gtk.TreeViewColumn("Name", renderer_text, text=1)
+        self.view.append_column(column_text)
+
+    def delete_all(self):
+        print "OC.delete_all()"
+        self.collection = []
+        self.active = None
+
+    def delete_active(self):
+        print "OC.delete_active()"
+        self.collection.remove(self.active)
+        self.active = None
+        self.build_list()
+
+    def on_row_activated(self, *args):
+        print "OC.on_row_activated()"
+        return
+
+    def on_list_selection_change(self, selection):
+        print "OC.on_list_selection_change()"
+        model, treeiter = selection.get_selected()
+
+        try:
+            self.get_active().read_form()
+        except:
+            pass
+
+        try:
+            self.set_active(model[treeiter][1])
+            self.get_active().build_ui()
+        except:
+            pass
+
     def set_active(self, name):
+        print "OC.set_active()"
         for obj in self.collection:
             if obj.options['name'] == name:
                 self.active = obj
@@ -2816,10 +2847,63 @@ class ObjectCollection:
         return None
 
     def get_active(self):
+        print "OC.get_active()"
         return self.active
 
-    def append(self, obj):
-        self.collection.append(obj)
+    def set_list_selection(self, name):
+        print "OC.set_list_selection()"
+        iterat = self.store.get_iter_first()
+        while iterat is not None and self.store[iterat][1] != name:
+            iterat = self.store.iter_next(iterat)
+        self.tree_selection.unselect_all()
+        self.tree_selection.select_iter(iterat)
+
+    def append(self, obj, active=False):
+        print "OC.append()"
+        if obj not in self.collection:
+            self.collection.append(obj)
+            self.build_list()
+
+        if active:
+            self.set_list_selection(obj.options["name"])
+
+    def get_names(self):
+        print "OC.get_names()"
+        return [o.options["name"] for o in self.collection]
+
+    def build_list(self):
+        print "OC.build_list()"
+        self.store.clear()
+        for obj in self.collection:
+            icon = GdkPixbuf.Pixbuf.new_from_file(ObjectCollection.icons[obj.kind])
+            self.store.append([icon, obj.options["name"]])
+
+    def get_bounds(self):
+        print "OC.get_bounds()"
+        return get_bounds(self.collection)
+
+    def get_list(self):
+        return self.collection
+
+    def get_by_name(self, name):
+        for obj in self.collection:
+            if obj.options["name"] == name:
+                return obj
+        return None
+
+    def change_name(self, old_name, new_name):
+        self.tree_selection.disconnect(self.change_subscription)
+
+        for obj in self.collection:
+            if obj.options["name"] == old_name:
+                obj.options["name"] = new_name
+                self.build_list()
+                self.tree_selection.connect("changed", self.on_list_selection_change)
+                if obj == self.get_active():
+                    self.set_active(new_name)
+                    self.set_list_selection(new_name)
+                break
+
 
 app = App()
 Gtk.main()

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 394 - 527
FlatCAM.ui


+ 2 - 2
FlatCAMObj.py

@@ -111,8 +111,8 @@ class FlatCAMObj:
         # Put in the UI
         box_selected.pack_start(sw, True, True, 0)
 
-        entry_name = self.app.builder.get_object("entry_text_" + self.kind + "_name")
-        entry_name.connect("activate", self.app.on_activate_name)
+        # entry_name = self.app.builder.get_object("entry_text_" + self.kind + "_name")
+        # entry_name.connect("activate", self.app.on_activate_name)
         self.to_form()
         sw.show()
 

+ 22 - 4
camlib.py

@@ -2251,16 +2251,35 @@ class CNCjob(Geometry):
         self.create_geometry()
 
 
-def get_bounds(geometry_set):
+# def get_bounds(geometry_set):
+#     xmin = Inf
+#     ymin = Inf
+#     xmax = -Inf
+#     ymax = -Inf
+#
+#     #print "Getting bounds of:", str(geometry_set)
+#     for gs in geometry_set:
+#         try:
+#             gxmin, gymin, gxmax, gymax = geometry_set[gs].bounds()
+#             xmin = min([xmin, gxmin])
+#             ymin = min([ymin, gymin])
+#             xmax = max([xmax, gxmax])
+#             ymax = max([ymax, gymax])
+#         except:
+#             print "DEV WARNING: Tried to get bounds of empty geometry."
+#
+#     return [xmin, ymin, xmax, ymax]
+
+def get_bounds(geometry_list):
     xmin = Inf
     ymin = Inf
     xmax = -Inf
     ymax = -Inf
 
     #print "Getting bounds of:", str(geometry_set)
-    for gs in geometry_set:
+    for gs in geometry_list:
         try:
-            gxmin, gymin, gxmax, gymax = geometry_set[gs].bounds()
+            gxmin, gymin, gxmax, gymax = gs.bounds()
             xmin = min([xmin, gxmin])
             ymin = min([ymin, gymin])
             xmax = max([xmax, gxmax])
@@ -2270,7 +2289,6 @@ def get_bounds(geometry_set):
 
     return [xmin, ymin, xmax, ymax]
 
-
 def arc(center, radius, start, stop, direction, steps_per_circ):
     """
     Creates a list of point along the specified arc.

+ 1 - 1
recent.json

@@ -1 +1 @@
-[{"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\BLDC2003Through.drl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\Project Outputs for RTWO1\\PCB1.GTL"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\BLDC_1303_Bottom.gbr"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\PlacaReles.drl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\PlacaReles-F_Cu.gtl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\Example1_copper_bottom.gbr"}, {"kind": "cncjob", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\RTWO1_CNC\\cutout1.gcode"}, {"kind": "project", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\RTWO1.fcproj"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\Project Outputs for RTWO1\\PCB1.TXT"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\Project Outputs for RTWO1\\PCB1.DRL"}]
+[{"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\Project Outputs for RTWO1\\PCB1.GBL"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\BLDC2003Through.drl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\Project Outputs for RTWO1\\PCB1.GTL"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\BLDC_1303_Bottom.gbr"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\PlacaReles.drl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\PlacaReles-F_Cu.gtl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\Example1_copper_bottom.gbr"}, {"kind": "cncjob", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\RTWO1_CNC\\cutout1.gcode"}, {"kind": "project", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\RTWO1.fcproj"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\Project Outputs for RTWO1\\PCB1.TXT"}]

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio