Jelajahi Sumber

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 11 tahun lalu
induk
melakukan
609561f7a3
5 mengubah file dengan 743 tambahan dan 774 penghapusan
  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 matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas
 #from mpl_toolkits.axes_grid.anchored_artists import AnchoredText
 #from mpl_toolkits.axes_grid.anchored_artists import AnchoredText
 
 
-from camlib import *
+
 import sys
 import sys
 import urllib
 import urllib
 import copy
 import copy
 import random
 import random
 
 
+########################################
+##      Imports part of FlatCAM       ##
+########################################
+from camlib import *
 from FlatCAMObj import *
 from FlatCAMObj import *
-
 from FlatCAMWorker import Worker
 from FlatCAMWorker import Worker
 
 
 ########################################
 ########################################
@@ -41,6 +44,8 @@ class App:
     The main application class. The constructor starts the GUI.
     The main application class. The constructor starts the GUI.
     """
     """
 
 
+    version_url = "http://caram.cl/flatcam/VERSION"
+
     def __init__(self):
     def __init__(self):
         """
         """
         Starts the application. Takes no parameters.
         Starts the application. Takes no parameters.
@@ -78,7 +83,7 @@ class App:
         self.combo_options = self.builder.get_object("combo_options")
         self.combo_options = self.builder.get_object("combo_options")
         self.combo_options.set_active(1)
         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
         self.setup_component_editor()  # The "Selected" tab
 
 
         ## Setup the toolbar. Adds buttons.
         ## Setup the toolbar. Adds buttons.
@@ -95,16 +100,20 @@ class App:
 
 
         self.setup_tooltips()
         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 ####
         #### DATA ####
         self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
         self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
         self.setup_obj_classes()
         self.setup_obj_classes()
-        self.stuff = {}    # FlatCAMObj's by name
         self.mouse = None  # Mouse coordinates over plot
         self.mouse = None  # Mouse coordinates over plot
         self.recent = []
         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
         # Used to inhibit the on_options_update callback when
         # the options are being changed by the program and not the user.
         # 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]})
                 #     self.radios_inv.update({obj.kind + "_" + option: obj.radios_inv[option]})
 
 
         ## Event subscriptions ##
         ## Event subscriptions ##
+        # TODO: Move to plotcanvas
         self.plot_click_subscribers = {}
         self.plot_click_subscribers = {}
         self.plot_mousemove_subscribers = {}
         self.plot_mousemove_subscribers = {}
 
 
@@ -168,11 +178,11 @@ class App:
         self.worker.start()
         self.worker.start()
 
 
         #### Check for updates ####
         #### Check for updates ####
+        # Separate thread (Not worker)
         self.version = 4
         self.version = 4
         t1 = threading.Thread(target=self.versionCheck)
         t1 = threading.Thread(target=self.versionCheck)
         t1.daemon = True
         t1.daemon = True
         t1.start()
         t1.start()
-        # self.worker.add_task(self.versionCheck)
 
 
         #### For debugging only ###
         #### For debugging only ###
         def somethreadfunc(app_obj):
         def somethreadfunc(app_obj):
@@ -245,27 +255,47 @@ class App:
         """
         """
         FlatCAMObj.app = self
         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):
     def setup_component_editor(self):
         """
         """
@@ -293,6 +323,7 @@ class App:
 
 
     def setup_recent_items(self):
     def setup_recent_items(self):
 
 
+        # TODO: Move this to constructor
         icons = {
         icons = {
             "gerber": "share/flatcam_icon16.png",
             "gerber": "share/flatcam_icon16.png",
             "excellon": "share/drill16.png",
             "excellon": "share/drill16.png",
@@ -309,13 +340,6 @@ class App:
 
 
         # Closure needed to create callbacks in a loop.
         # Closure needed to create callbacks in a loop.
         # Otherwise late binding occurs.
         # 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 make_callback(func, fname):
             def opener(*args):
             def opener(*args):
                 self.worker.add_task(func, [fname])
                 self.worker.add_task(func, [fname])
@@ -362,21 +386,31 @@ class App:
         """
         """
         self.info_label.set_text(text)
         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):
     def get_radio_value(self, radio_set):
         """
         """
@@ -401,26 +435,24 @@ class App:
         self.plotcanvas.clear()
         self.plotcanvas.clear()
         self.set_progress_bar(0.1, "Re-plotting...")
         self.set_progress_bar(0.1, "Re-plotting...")
 
 
-        def thread_func(app_obj):
+        def worker_task(app_obj):
             percentage = 0.1
             percentage = 0.1
             try:
             try:
-                delta = 0.9 / len(self.stuff)
+                delta = 0.9 / len(self.collection.get_list())
             except ZeroDivisionError:
             except ZeroDivisionError:
                 GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, ""))
                 GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, ""))
                 return
                 return
-            for i in self.stuff:
-                self.stuff[i].plot()
+            for obj in self.collection.get_list():
+                obj.plot()
                 percentage += delta
                 percentage += delta
                 GLib.idle_add(lambda: app_obj.set_progress_bar(percentage, "Re-plotting..."))
                 GLib.idle_add(lambda: app_obj.set_progress_bar(percentage, "Re-plotting..."))
 
 
             GLib.idle_add(app_obj.plotcanvas.auto_adjust_axes)
             GLib.idle_add(app_obj.plotcanvas.auto_adjust_axes)
             GLib.idle_add(lambda: self.on_zoom_fit(None))
             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):
     def get_eval(self, widget_name):
         """
         """
@@ -442,26 +474,26 @@ class App:
             self.info("Could not evaluate: " + value)
             self.info("Could not evaluate: " + value)
             return None
             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):
     def new_object(self, kind, name, initialize):
         """
         """
@@ -482,7 +514,7 @@ class App:
         """
         """
 
 
         ### Check for existing name
         ### Check for existing name
-        if name in self.stuff:
+        if name in self.collection.get_names():
             ## Create a new name
             ## Create a new name
             # Ends with number?
             # Ends with number?
             match = re.search(r'(.*[^\d])?(\d+)$', name)
             match = re.search(r'(.*[^\d])?(\d+)$', name)
@@ -503,12 +535,6 @@ class App:
         obj = classdict[kind](name)
         obj = classdict[kind](name)
         obj.units = self.options["units"]  # TODO: The constructor should look at defaults.
         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
         # Set default options from self.options
         for option in self.options:
         for option in self.options:
             if option.find(kind + "_") == 0:
             if option.find(kind + "_") == 0:
@@ -527,14 +553,10 @@ class App:
             obj.convert_units(self.options["units"])
             obj.convert_units(self.options["units"])
 
 
         # Add to our records
         # 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))
         GLib.timeout_add(100, lambda: self.notebook.set_current_page(1))
 
 
         # Plot
         # Plot
@@ -560,22 +582,22 @@ class App:
         self.progress_bar.set_fraction(percentage)
         self.progress_bar.set_fraction(percentage)
         return False
         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):
     def load_defaults(self):
         """
         """
@@ -712,11 +734,11 @@ class App:
 
 
         # Capture the latest changes
         # Capture the latest changes
         try:
         try:
-            self.get_current().read_form()
+            self.collection.get_active().read_form()
         except:
         except:
             pass
             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}
              "options": self.options}
 
 
         try:
         try:
@@ -746,15 +768,12 @@ class App:
         try:
         try:
             f = open(filename, 'r')
             f = open(filename, 'r')
         except:
         except:
-            #print "WARNING: Failed to open project file:", filename
             self.info("ERROR: Failed to open project file: %s" % filename)
             self.info("ERROR: Failed to open project file: %s" % filename)
             return
             return
 
 
         try:
         try:
             d = json.load(f, object_hook=dict2obj)
             d = json.load(f, object_hook=dict2obj)
         except:
         except:
-            #print sys.exc_info()
-            #print "WARNING: Failed to parse project file:", filename
             self.info("ERROR: Failed to parse project file: %s" % filename)
             self.info("ERROR: Failed to parse project file: %s" % filename)
             f.close()
             f.close()
             return
             return
@@ -790,8 +809,8 @@ class App:
             combo = self.builder.get_object(combo)
             combo = self.builder.get_object(combo)
 
 
         combo.remove_all()
         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):
     def versionCheck(self, *args):
         """
         """
@@ -803,7 +822,7 @@ class App:
         """
         """
 
 
         try:
         try:
-            f = urllib.urlopen("http://caram.cl/flatcam/VERSION")  # TODO: Hardcoded.
+            f = urllib.urlopen(App.version_url)
         except:
         except:
             GLib.idle_add(lambda: self.info("ERROR trying to check for latest version."))
             GLib.idle_add(lambda: self.info("ERROR trying to check for latest version."))
             return
             return
@@ -868,52 +887,49 @@ class App:
 
 
         self.set_progress_bar(0.1, "Re-plotting...")
         self.set_progress_bar(0.1, "Re-plotting...")
 
 
-        def thread_func(app_obj):
+        def worker_task(app_obj):
             percentage = 0.1
             percentage = 0.1
             try:
             try:
-                delta = 0.9 / len(self.stuff)
+                delta = 0.9 / len(self.collection.get_list())
             except ZeroDivisionError:
             except ZeroDivisionError:
                 GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, ""))
                 GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, ""))
                 return
                 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
                 percentage += delta
                 GLib.idle_add(lambda: app_obj.set_progress_bar(percentage, "Re-plotting..."))
                 GLib.idle_add(lambda: app_obj.set_progress_bar(percentage, "Re-plotting..."))
 
 
             GLib.idle_add(app_obj.plotcanvas.auto_adjust_axes)
             GLib.idle_add(app_obj.plotcanvas.auto_adjust_axes)
             GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, ""))
             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):
     def enable_all_plots(self, *args):
         self.plotcanvas.clear()
         self.plotcanvas.clear()
         self.set_progress_bar(0.1, "Re-plotting...")
         self.set_progress_bar(0.1, "Re-plotting...")
 
 
-        def thread_func(app_obj):
+        def worker_task(app_obj):
             percentage = 0.1
             percentage = 0.1
             try:
             try:
-                delta = 0.9 / len(self.stuff)
+                delta = 0.9 / len(self.collection.get_list())
             except ZeroDivisionError:
             except ZeroDivisionError:
                 GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, ""))
                 GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, ""))
                 return
                 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
                 percentage += delta
                 GLib.idle_add(lambda: app_obj.set_progress_bar(percentage, "Re-plotting..."))
                 GLib.idle_add(lambda: app_obj.set_progress_bar(percentage, "Re-plotting..."))
 
 
             GLib.idle_add(app_obj.plotcanvas.auto_adjust_axes)
             GLib.idle_add(app_obj.plotcanvas.auto_adjust_axes)
             GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, ""))
             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):
     def register_recent(self, kind, filename):
         record = {'kind': kind, 'filename': filename}
         record = {'kind': kind, 'filename': filename}
@@ -960,7 +976,7 @@ class App:
         :return: None
         :return: None
         """
         """
 
 
-        obj = self.get_current()
+        obj = self.collection.get_active()
         obj.read_form()
         obj.read_form()
         assert isinstance(obj, FlatCAMObj)
         assert isinstance(obj, FlatCAMObj)
         try:
         try:
@@ -981,8 +997,10 @@ class App:
         :return: None
         :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):
     def on_about(self, widget):
         """
         """
@@ -1007,12 +1025,8 @@ class App:
         # TODO: Move (some of) this to camlib!
         # TODO: Move (some of) this to camlib!
 
 
         # Object to mirror
         # 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.
         # For now, lets limit to Gerbers and Excellons.
         # assert isinstance(gerb, FlatCAMGerber)
         # assert isinstance(gerb, FlatCAMGerber)
@@ -1030,23 +1044,13 @@ class App:
             px, py = eval(self.point_entry.get_text())
             px, py = eval(self.point_entry.get_text())
         else:  # The axis is the line dividing the box in the middle
         else:  # The axis is the line dividing the box in the middle
             name = self.box_combo.get_active_text()
             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()
             xmin, ymin, xmax, ymax = bb_obj.bounds()
             px = 0.5*(xmin+xmax)
             px = 0.5*(xmin+xmax)
             py = 0.5*(ymin+ymax)
             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.mirror(axis, [px, py])
         fcobj.plot()
         fcobj.plot()
-        #self.on_update_plot(None)
 
 
     def on_create_aligndrill(self, widget):
     def on_create_aligndrill(self, widget):
         """
         """
@@ -1067,7 +1071,7 @@ class App:
             px, py = eval(self.point_entry.get_text())
             px, py = eval(self.point_entry.get_text())
         else:
         else:
             name = self.box_combo.get_active_text()
             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()
             xmin, ymin, xmax, ymax = bb_obj.bounds()
             px = 0.5*(xmin+xmax)
             px = 0.5*(xmin+xmax)
             py = 0.5*(ymin+ymax)
             py = 0.5*(ymin+ymax)
@@ -1232,11 +1236,10 @@ class App:
             self.read_form()
             self.read_form()
             scale_options(factor)
             scale_options(factor)
             self.options2form()
             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"})
                 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:
             if current is not None:
                 current.to_form()
                 current.to_form()
             self.plot_all()
             self.plot_all()
@@ -1355,7 +1358,7 @@ class App:
         :return: None
         :return: None
         """
         """
 
 
-        obj = self.get_current()
+        obj = self.collection.get_active()
         if obj is None:
         if obj is None:
             self.info("WARNING: No object selected.")
             self.info("WARNING: No object selected.")
             return
             return
@@ -1374,7 +1377,7 @@ class App:
         :return: None
         :return: None
         """
         """
 
 
-        obj = self.get_current()
+        obj = self.collection.get_active()
         if obj is None:
         if obj is None:
             self.info("WARNING: No object selected.")
             self.info("WARNING: No object selected.")
             return
             return
@@ -1393,7 +1396,7 @@ class App:
         :param param: Ignored.
         :param param: Ignored.
         :return: None
         :return: None
         """
         """
-        obj = self.get_current()
+        obj = self.collection.get_active()
         if obj is None:
         if obj is None:
             self.info("WARNING: No object selected.")
             self.info("WARNING: No object selected.")
             return
             return
@@ -1413,7 +1416,7 @@ class App:
         :return: None
         :return: None
         """
         """
 
 
-        obj = self.get_current()
+        obj = self.collection.get_active()
         if obj is None:
         if obj is None:
             self.info("WARNING: No object selected.")
             self.info("WARNING: No object selected.")
             return
             return
@@ -1501,7 +1504,7 @@ class App:
         :return: None
         :return: None
         """
         """
 
 
-        obj = self.get_current()
+        obj = self.collection.get_active()
         factor = self.get_eval("entry_eval_" + obj.kind + "_scalefactor")
         factor = self.get_eval("entry_eval_" + obj.kind + "_scalefactor")
         obj.scale(factor)
         obj.scale(factor)
         obj.to_form()
         obj.to_form()
@@ -1542,7 +1545,7 @@ class App:
         :return: None
         :return: None
         """
         """
         # TODO: Use Gerber.get_bounding_box(...)
         # TODO: Use Gerber.get_bounding_box(...)
-        gerber = self.get_current()
+        gerber = self.collection.get_active()
         gerber.read_form()
         gerber.read_form()
         name = gerber.options["name"] + "_bbox"
         name = gerber.options["name"] + "_bbox"
 
 
@@ -1565,7 +1568,7 @@ class App:
         :return: None
         :return: None
         """
         """
 
 
-        obj = self.get_current()
+        obj = self.collection.get_active()
         obj.read_form()
         obj.read_form()
 
 
         self.set_progress_bar(0.5, "Plotting...")
         self.set_progress_bar(0.5, "Plotting...")
@@ -1589,7 +1592,7 @@ class App:
         :return: None
         :return: None
         """
         """
 
 
-        excellon = self.get_current()
+        excellon = self.collection.get_active()
         excellon.read_form()
         excellon.read_form()
         job_name = excellon.options["name"] + "_cnc"
         job_name = excellon.options["name"] + "_cnc"
 
 
@@ -1636,7 +1639,7 @@ class App:
         :param widget: The widget from which this was called.
         :param widget: The widget from which this was called.
         :return: None
         :return: None
         """
         """
-        excellon = self.get_current()
+        excellon = self.collection.get_active()
         assert isinstance(excellon, FlatCAMExcellon)
         assert isinstance(excellon, FlatCAMExcellon)
         excellon.show_tool_chooser()
         excellon.show_tool_chooser()
 
 
@@ -1650,7 +1653,7 @@ class App:
         :return:
         :return:
         """
         """
         self.on_eval_update(widget)
         self.on_eval_update(widget)
-        obj = self.get_current()
+        obj = self.collection.get_active()
         assert isinstance(obj, FlatCAMObj)
         assert isinstance(obj, FlatCAMObj)
         obj.read_form()
         obj.read_form()
 
 
@@ -1664,7 +1667,7 @@ class App:
         :return: None
         :return: None
         """
         """
 
 
-        gerb = self.get_current()
+        gerb = self.collection.get_active()
         gerb.read_form()
         gerb.read_form()
         name = gerb.options["name"] + "_noncopper"
         name = gerb.options["name"] + "_noncopper"
 
 
@@ -1688,7 +1691,7 @@ class App:
         :return: None
         :return: None
         """
         """
 
 
-        gerb = self.get_current()
+        gerb = self.collection.get_active()
         gerb.read_form()
         gerb.read_form()
         name = gerb.options["name"] + "_cutout"
         name = gerb.options["name"] + "_cutout"
 
 
@@ -1749,7 +1752,7 @@ class App:
         :return: None
         :return: None
         """
         """
 
 
-        gerb = self.get_current()
+        gerb = self.collection.get_active()
         gerb.read_form()
         gerb.read_form()
         dia = gerb.options["isotooldia"]
         dia = gerb.options["isotooldia"]
         passes = int(gerb.options["isopasses"])
         passes = int(gerb.options["isopasses"])
@@ -1779,7 +1782,7 @@ class App:
         :return: None
         :return: None
         """
         """
 
 
-        source_geo = self.get_current()
+        source_geo = self.collection.get_active()
         source_geo.read_form()
         source_geo.read_form()
         job_name = source_geo.options["name"] + "_cnc"
         job_name = source_geo.options["name"] + "_cnc"
 
 
@@ -1834,7 +1837,7 @@ class App:
         """
         """
 
 
         self.info("Click inside the desired polygon.")
         self.info("Click inside the desired polygon.")
-        geo = self.get_current()
+        geo = self.collection.get_active()
         geo.read_form()
         geo.read_form()
         assert isinstance(geo, FlatCAMGeometry)
         assert isinstance(geo, FlatCAMGeometry)
         tooldia = geo.options["painttooldia"]
         tooldia = geo.options["painttooldia"]
@@ -1868,7 +1871,7 @@ class App:
         :return: None
         :return: None
         """
         """
         def on_success(app_obj, filename):
         def on_success(app_obj, filename):
-            cncjob = app_obj.get_current()
+            cncjob = app_obj.collection.get_active()
             f = open(filename, 'w')
             f = open(filename, 'w')
             f.write(cncjob.gcode)
             f.write(cncjob.gcode)
             f.close()
             f.close()
@@ -1885,17 +1888,17 @@ class App:
         """
         """
 
 
         # Keep this for later
         # Keep this for later
-        name = copy.copy(self.selected_item_name)
+        name = copy.copy(self.collection.get_active().options["name"])
 
 
         # Remove plot
         # Remove plot
-        self.plotcanvas.figure.delaxes(self.get_current().axes)
+        self.plotcanvas.figure.delaxes(self.collection.get_active().axes)
         self.plotcanvas.auto_adjust_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)
         self.info("Object deleted: %s" % name)
 
 
@@ -1907,7 +1910,7 @@ class App:
         :return: None
         :return: None
         """
         """
 
 
-        self.get_current().read_form()
+        self.collection.get_active().read_form()
 
 
         self.plot_all()
         self.plot_all()
 
 
@@ -1929,49 +1932,10 @@ class App:
         :return: None
         :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):
     def on_file_new(self, param):
         """
         """
@@ -1981,19 +1945,18 @@ class App:
         :param param: Whatever is passed by the event. Ignore.
         :param param: Whatever is passed by the event. Ignore.
         :return: None
         :return: None
         """
         """
-        # Remove everythong from memory
+        # Remove everything from memory
         # Clear plot
         # Clear plot
         self.plotcanvas.clear()
         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
         # Clear list
-        #self.tree_select.unselect_all()
-        self.build_list()
+        self.collection.build_list()
 
 
         # Clear project filename
         # Clear project filename
         self.project_filename = None
         self.project_filename = None
@@ -2339,7 +2302,7 @@ class App:
         :param event: Ignored.
         :param event: Ignored.
         :return: None
         :return: None
         """
         """
-        xmin, ymin, xmax, ymax = get_bounds(self.stuff)
+        xmin, ymin, xmax, ymax = self.collection.get_bounds()
         width = xmax - xmin
         width = xmax - xmin
         height = ymax - ymin
         height = ymax - ymin
         xmin -= 0.05 * width
         xmin -= 0.05 * width
@@ -2804,11 +2767,79 @@ class PlotCanvas:
 
 
 
 
 class ObjectCollection:
 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):
     def __init__(self):
+
+        ### Data
+        # List of FLatCAMObject's
         self.collection = []
         self.collection = []
         self.active = None
         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):
     def set_active(self, name):
+        print "OC.set_active()"
         for obj in self.collection:
         for obj in self.collection:
             if obj.options['name'] == name:
             if obj.options['name'] == name:
                 self.active = obj
                 self.active = obj
@@ -2816,10 +2847,63 @@ class ObjectCollection:
         return None
         return None
 
 
     def get_active(self):
     def get_active(self):
+        print "OC.get_active()"
         return self.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()
 app = App()
 Gtk.main()
 Gtk.main()

File diff ditekan karena terlalu besar
+ 394 - 527
FlatCAM.ui


+ 2 - 2
FlatCAMObj.py

@@ -111,8 +111,8 @@ class FlatCAMObj:
         # Put in the UI
         # Put in the UI
         box_selected.pack_start(sw, True, True, 0)
         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()
         self.to_form()
         sw.show()
         sw.show()
 
 

+ 22 - 4
camlib.py

@@ -2251,16 +2251,35 @@ class CNCjob(Geometry):
         self.create_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
     xmin = Inf
     ymin = Inf
     ymin = Inf
     xmax = -Inf
     xmax = -Inf
     ymax = -Inf
     ymax = -Inf
 
 
     #print "Getting bounds of:", str(geometry_set)
     #print "Getting bounds of:", str(geometry_set)
-    for gs in geometry_set:
+    for gs in geometry_list:
         try:
         try:
-            gxmin, gymin, gxmax, gymax = geometry_set[gs].bounds()
+            gxmin, gymin, gxmax, gymax = gs.bounds()
             xmin = min([xmin, gxmin])
             xmin = min([xmin, gxmin])
             ymin = min([ymin, gymin])
             ymin = min([ymin, gymin])
             xmax = max([xmax, gxmax])
             xmax = max([xmax, gxmax])
@@ -2270,7 +2289,6 @@ def get_bounds(geometry_set):
 
 
     return [xmin, ymin, xmax, ymax]
     return [xmin, ymin, xmax, ymax]
 
 
-
 def arc(center, radius, start, stop, direction, steps_per_circ):
 def arc(center, radius, start, stop, direction, steps_per_circ):
     """
     """
     Creates a list of point along the specified arc.
     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"}]

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini