Просмотр исходного кода

Warning before overwriting. More flexible Excellon parser (tool numbers). Other small fixes.

Juan Pablo Caram 11 лет назад
Родитель
Сommit
6c13b7dc59

+ 228 - 272
FlatCAM.py

@@ -36,6 +36,7 @@ from camlib import *
 from FlatCAMObj import *
 from FlatCAMObj import *
 from FlatCAMWorker import Worker
 from FlatCAMWorker import Worker
 
 
+
 ########################################
 ########################################
 ##                App                 ##
 ##                App                 ##
 ########################################
 ########################################
@@ -149,13 +150,9 @@ 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_mousemove_subscribers = {}
 
 
         ## Tools ##
         ## Tools ##
-        self.measure = Measurement(self.builder.get_object("box39"), self.plotcanvas.axes,
-                                   self.plot_click_subscribers, self.plot_mousemove_subscribers)
+        self.measure = Measurement(self.builder.get_object("box39"), self.plotcanvas)
         # Toolbar icon
         # Toolbar icon
         # TODO: Where should I put this? Tool should have a method to add to toolbar?
         # TODO: Where should I put this? Tool should have a method to add to toolbar?
         meas_ico = Gtk.Image.new_from_file('share/measure32.png')
         meas_ico = Gtk.Image.new_from_file('share/measure32.png')
@@ -188,10 +185,13 @@ class App:
         def somethreadfunc(app_obj):
         def somethreadfunc(app_obj):
             print "Hello World!"
             print "Hello World!"
 
 
+        self.message_dialog("Starting", "The best program is starting")
+
         t = threading.Thread(target=somethreadfunc, args=(self,))
         t = threading.Thread(target=somethreadfunc, args=(self,))
         t.daemon = True
         t.daemon = True
         t.start()
         t.start()
 
 
+
         ########################################
         ########################################
         ##              START                 ##
         ##              START                 ##
         ########################################
         ########################################
@@ -203,6 +203,30 @@ class App:
         self.window.set_default_size(900, 600)
         self.window.set_default_size(900, 600)
         self.window.show_all()
         self.window.show_all()
 
 
+    def message_dialog(self, title, message, type="info"):
+        types = {"info": Gtk.MessageType.INFO,
+                 "warn": Gtk.MessageType.WARNING,
+                 "error": Gtk.MessageType.ERROR}
+        dlg = Gtk.MessageDialog(self.window, 0, types[type], Gtk.ButtonsType.OK, title)
+        dlg.format_secondary_text(message)
+        dlg.run()
+        dlg.destroy()
+
+    def question_dialog(self, title, message):
+        label = Gtk.Label(message)
+        dialog = Gtk.Dialog(title, self.window, 0,
+                            (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
+                             Gtk.STOCK_OK, Gtk.ResponseType.OK))
+        dialog.set_default_size(150, 100)
+        dialog.set_modal(True)
+        box = dialog.get_content_area()
+        box.set_border_width(10)
+        box.add(label)
+        dialog.show_all()
+        response = dialog.run()
+        dialog.destroy()
+        return response
+
     def setup_toolbar(self):
     def setup_toolbar(self):
 
 
         # Zoom fit
         # Zoom fit
@@ -255,48 +279,6 @@ 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
-    #     """
-    #
-    #     # 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):
         """
         """
         Initial configuration of the component editor. Creates
         Initial configuration of the component editor. Creates
@@ -335,7 +317,7 @@ class App:
             'gerber': self.open_gerber,
             'gerber': self.open_gerber,
             'excellon': self.open_excellon,
             'excellon': self.open_excellon,
             'cncjob': self.open_gcode,
             'cncjob': self.open_gcode,
-            'project': lambda x: ""
+            'project': self.open_project
         }
         }
 
 
         # Closure needed to create callbacks in a loop.
         # Closure needed to create callbacks in a loop.
@@ -378,39 +360,13 @@ class App:
 
 
     def info(self, text):
     def info(self, text):
         """
         """
-        Show text on the status bar.
+        Show text on the status bar. This method is thread safe.
 
 
         :param text: Text to display.
         :param text: Text to display.
         :type text: str
         :type text: str
         :return: None
         :return: None
         """
         """
-        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
-    #     """
-    #     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
+        GLib.idle_add(lambda: self.info_label.set_text(text))
 
 
     def get_radio_value(self, radio_set):
     def get_radio_value(self, radio_set):
         """
         """
@@ -474,31 +430,11 @@ 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][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):
         """
         """
         Creates a new specalized FlatCAMObj and attaches it to the application,
         Creates a new specalized FlatCAMObj and attaches it to the application,
         this is, updates the GUI accordingly, any other records and plots it.
         this is, updates the GUI accordingly, any other records and plots it.
+        This method is thread-safe.
 
 
         :param kind: The kind of object to create. One of 'gerber',
         :param kind: The kind of object to create. One of 'gerber',
          'excellon', 'cncjob' and 'geometry'.
          'excellon', 'cncjob' and 'geometry'.
@@ -513,6 +449,8 @@ class App:
         :rtype: None
         :rtype: None
         """
         """
 
 
+        print "new_object()"
+
         ### Check for existing name
         ### Check for existing name
         if name in self.collection.get_names():
         if name in self.collection.get_names():
             ## Create a new name
             ## Create a new name
@@ -553,18 +491,17 @@ class App:
             obj.convert_units(self.options["units"])
             obj.convert_units(self.options["units"])
 
 
         # Add to our records
         # Add to our records
-        # TODO: Perhaps make collection thread safe instead?
-        GLib.idle_add(lambda: self.collection.append(obj, active=True))
+        self.collection.append(obj, active=True)
 
 
         # Show object details now.
         # Show object details now.
-        GLib.timeout_add(100, lambda: self.notebook.set_current_page(1))
+        GLib.idle_add(lambda: self.notebook.set_current_page(1))
 
 
         # Plot
         # Plot
         # TODO: (Thread-safe?)
         # TODO: (Thread-safe?)
         obj.plot()
         obj.plot()
 
 
-        # TODO: Threading dissaster!
         GLib.idle_add(lambda: self.on_zoom_fit(None))
         GLib.idle_add(lambda: self.on_zoom_fit(None))
+        #self.on_zoom_fit(None)
 
 
         return obj
         return obj
 
 
@@ -582,23 +519,6 @@ 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 load_defaults(self):
     def load_defaults(self):
         """
         """
         Loads the aplication's default settings from defaults.json into
         Loads the aplication's default settings from defaults.json into
@@ -738,6 +658,7 @@ class App:
         except:
         except:
             pass
             pass
 
 
+        # Serialize the whole project
         d = {"objs": [obj.to_dict() for obj in self.collection.get_list()],
         d = {"objs": [obj.to_dict() for obj in self.collection.get_list()],
              "options": self.options}
              "options": self.options}
 
 
@@ -786,7 +707,7 @@ class App:
         # Project options
         # Project options
         self.options.update(d['options'])
         self.options.update(d['options'])
         self.project_filename = filename
         self.project_filename = filename
-        self.units_label.set_text(self.options["units"])
+        GLib.idle_add(lambda: self.units_label.set_text(self.options["units"]))
 
 
         # Re create objects
         # Re create objects
         for obj in d['objs']:
         for obj in d['objs']:
@@ -961,6 +882,9 @@ class App:
     ########################################
     ########################################
     ##         EVENT HANDLERS             ##
     ##         EVENT HANDLERS             ##
     ########################################
     ########################################
+    def on_debug_printlist(self, *args):
+        self.collection.print_list()
+
     def on_disable_all_plots(self, widget):
     def on_disable_all_plots(self, widget):
         self.disable_plots()
         self.disable_plots()
 
 
@@ -997,8 +921,6 @@ class App:
         :return: None
         :return: None
         """
         """
 
 
-        # self.get_current().read_form()
-        # self.get_current().plot()
         self.collection.get_active().read_form()
         self.collection.get_active().read_form()
         self.collection.get_active().plot()
         self.collection.get_active().plot()
 
 
@@ -1012,7 +934,6 @@ class App:
 
 
         about = self.builder.get_object("aboutdialog")
         about = self.builder.get_object("aboutdialog")
         response = about.run()
         response = about.run()
-        #about.destroy()
         about.hide()
         about.hide()
 
 
     def on_create_mirror(self, widget):
     def on_create_mirror(self, widget):
@@ -1299,6 +1220,18 @@ class App:
 
 
         def on_success(app_obj, filename):
         def on_success(app_obj, filename):
             assert isinstance(app_obj, App)
             assert isinstance(app_obj, App)
+
+            try:
+                f = open(filename, 'r')
+                f.close()
+                exists = True
+            except IOError:
+                exists = False
+
+            msg = "File exists. Overwrite?"
+            if exists and self.question_dialog("File exists", msg) == Gtk.ResponseType.CANCEL:
+                return
+
             app_obj.save_project(filename)
             app_obj.save_project(filename)
             self.project_filename = filename
             self.project_filename = filename
             self.register_recent("project", filename)
             self.register_recent("project", filename)
@@ -1319,6 +1252,18 @@ class App:
 
 
         def on_success(app_obj, filename):
         def on_success(app_obj, filename):
             assert isinstance(app_obj, App)
             assert isinstance(app_obj, App)
+
+            try:
+                f = open(filename, 'r')
+                f.close()
+                exists = True
+            except IOError:
+                exists = False
+
+            msg = "File exists. Overwrite?"
+            if exists and self.question_dialog("File exists", msg) == Gtk.ResponseType.CANCEL:
+                return
+
             app_obj.save_project(filename)
             app_obj.save_project(filename)
             self.register_recent("project", filename)
             self.register_recent("project", filename)
             app_obj.info("Project copy saved to: " + filename)
             app_obj.info("Project copy saved to: " + filename)
@@ -1578,9 +1523,7 @@ class App:
             obj.plot()
             obj.plot()
             GLib.timeout_add(300, lambda: app_obj.set_progress_bar(0.0, "Idle"))
             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()
+        # Send to worker
         self.worker.add_task(thread_func, [self])
         self.worker.add_task(thread_func, [self])
 
 
     def on_generate_excellon_cncjob(self, widget):
     def on_generate_excellon_cncjob(self, widget):
@@ -1625,10 +1568,7 @@ class App:
             GLib.idle_add(lambda: app_obj.set_progress_bar(1.0, "Done!"))
             GLib.idle_add(lambda: app_obj.set_progress_bar(1.0, "Done!"))
             GLib.timeout_add_seconds(1, lambda: app_obj.set_progress_bar(0.0, ""))
             GLib.timeout_add_seconds(1, lambda: app_obj.set_progress_bar(0.0, ""))
 
 
-        # Start the thread
-        # t = threading.Thread(target=job_thread, args=(self,))
-        # t.daemon = True
-        # t.start()
+        # Send to worker
         self.worker.add_task(job_thread, [self])
         self.worker.add_task(job_thread, [self])
 
 
     def on_excellon_tool_choose(self, widget):
     def on_excellon_tool_choose(self, widget):
@@ -1818,10 +1758,7 @@ class App:
             GLib.idle_add(lambda: app_obj.set_progress_bar(1.0, "Done!"))
             GLib.idle_add(lambda: app_obj.set_progress_bar(1.0, "Done!"))
             GLib.timeout_add_seconds(1, lambda: app_obj.set_progress_bar(0.0, ""))
             GLib.timeout_add_seconds(1, lambda: app_obj.set_progress_bar(0.0, ""))
 
 
-        # Start the thread
-        # t = threading.Thread(target=job_thread, args=(self,))
-        # t.daemon = True
-        # t.start()
+        # Send to worker
         self.worker.add_task(job_thread, [self])
         self.worker.add_task(job_thread, [self])
 
 
     def on_generate_paintarea(self, widget):
     def on_generate_paintarea(self, widget):
@@ -1843,10 +1780,14 @@ class App:
         tooldia = geo.options["painttooldia"]
         tooldia = geo.options["painttooldia"]
         overlap = geo.options["paintoverlap"]
         overlap = geo.options["paintoverlap"]
 
 
+        # Connection ID for the click event
+        subscription = None
+
         # To be called after clicking on the plot.
         # To be called after clicking on the plot.
         def doit(event):
         def doit(event):
-            self.plot_click_subscribers.pop("generate_paintarea")
-            self.info("")
+            #self.plot_click_subscribers.pop("generate_paintarea")
+            self.plotcanvas.mpl_disconnect(subscription)
+            self.info("Painting")
             point = [event.xdata, event.ydata]
             point = [event.xdata, event.ydata]
             poly = find_polygon(geo.solid_geometry, point)
             poly = find_polygon(geo.solid_geometry, point)
 
 
@@ -1858,10 +1799,12 @@ class App:
                 geo_obj.solid_geometry = cp
                 geo_obj.solid_geometry = cp
                 geo_obj.options["cnctooldia"] = tooldia
                 geo_obj.options["cnctooldia"] = tooldia
 
 
-            name = self.selected_item_name + "_paint"
+            #name = self.selected_item_name + "_paint"
+            name = geo.options["name"] + "_paint"
             self.new_object("geometry", name, gen_paintarea)
             self.new_object("geometry", name, gen_paintarea)
 
 
-        self.plot_click_subscribers["generate_paintarea"] = doit
+        #self.plot_click_subscribers["generate_paintarea"] = doit
+        subscription = self.plotcanvas.mpl_connect('button_press_event', doit)
 
 
     def on_cncjob_exportgcode(self, widget):
     def on_cncjob_exportgcode(self, widget):
         """
         """
@@ -1940,23 +1883,25 @@ class App:
     def on_file_new(self, param):
     def on_file_new(self, param):
         """
         """
         Callback for menu item File->New. Returns the application to its
         Callback for menu item File->New. Returns the application to its
-        startup state.
+        startup state. This method is thread-safe.
 
 
         :param param: Whatever is passed by the event. Ignore.
         :param param: Whatever is passed by the event. Ignore.
         :return: None
         :return: None
         """
         """
         # Remove everything from memory
         # Remove everything from memory
-        # Clear plot
-        self.plotcanvas.clear()
 
 
-        # Delete data
-        self.collection.delete_all()
+        # GUI things
+        def task():
+            # Clear plot
+            self.plotcanvas.clear()
 
 
-        # Clear object editor
-        self.setup_component_editor()
+            # Delete data
+            self.collection.delete_all()
+
+            # Clear object editor
+            self.setup_component_editor()
 
 
-        # Clear list
-        #self.collection.build_list()
+        GLib.idle_add(task)
 
 
         # Clear project filename
         # Clear project filename
         self.project_filename = None
         self.project_filename = None
@@ -2006,13 +1951,10 @@ class App:
         if response == Gtk.ResponseType.OK:
         if response == Gtk.ResponseType.OK:
             filename = dialog.get_filename()
             filename = dialog.get_filename()
             dialog.destroy()
             dialog.destroy()
-            # t = threading.Thread(target=on_success, args=(self, filename))
-            # t.daemon = True
-            # t.start()
+            # Send to worker.
             self.worker.add_task(on_success, [self, filename])
             self.worker.add_task(on_success, [self, filename])
-            #on_success(self, filename)
         elif response == Gtk.ResponseType.CANCEL:
         elif response == Gtk.ResponseType.CANCEL:
-            self.info("Open cancelled.")  # print("Cancel clicked")
+            self.info("Open cancelled.")
             dialog.destroy()
             dialog.destroy()
 
 
     def file_chooser_save_action(self, on_success):
     def file_chooser_save_action(self, on_success):
@@ -2043,8 +1985,12 @@ class App:
 
 
         def obj_init(gerber_obj, app_obj):
         def obj_init(gerber_obj, app_obj):
             assert isinstance(gerber_obj, FlatCAMGerber)
             assert isinstance(gerber_obj, FlatCAMGerber)
+
+            # Opening the file happens here
             GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Parsing ..."))
             GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Parsing ..."))
             gerber_obj.parse_file(filename)
             gerber_obj.parse_file(filename)
+
+            # Further parsing
             GLib.idle_add(lambda: app_obj.set_progress_bar(0.5, "Creating Geometry ..."))
             GLib.idle_add(lambda: app_obj.set_progress_bar(0.5, "Creating Geometry ..."))
             gerber_obj.create_geometry()
             gerber_obj.create_geometry()
             GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Plotting ..."))
             GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Plotting ..."))
@@ -2053,8 +1999,9 @@ class App:
         self.new_object("gerber", name, obj_init)
         self.new_object("gerber", name, obj_init)
         self.register_recent("gerber", filename)
         self.register_recent("gerber", filename)
 
 
+        self.info("Opened: " + filename)
         GLib.idle_add(lambda: self.set_progress_bar(1.0, "Done!"))
         GLib.idle_add(lambda: self.set_progress_bar(1.0, "Done!"))
-        GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, ""))
+        GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, "Idle"))
 
 
     def on_fileopengerber(self, param):
     def on_fileopengerber(self, param):
         """
         """
@@ -2065,30 +2012,7 @@ class App:
         :param param: Ignore
         :param param: Ignore
         :return: None
         :return: None
         """
         """
-        # IMPORTANT: on_success will run on a separate thread. Use
-        # GLib.idle_add(function, **kwargs) to launch actions that will
-        # updata the GUI.
-        # def on_success(app_obj, filename):
-        #     assert isinstance(app_obj, App)
-        #     GLib.idle_add(lambda: app_obj.set_progress_bar(0.1, "Opening Gerber ..."))
-        #
-        #     def obj_init(gerber_obj, app_obj):
-        #         assert isinstance(gerber_obj, FlatCAMGerber)
-        #         GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Parsing ..."))
-        #         gerber_obj.parse_file(filename)
-        #         GLib.idle_add(lambda: app_obj.set_progress_bar(0.5, "Creating Geometry ..."))
-        #         gerber_obj.create_geometry()
-        #         GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Plotting ..."))
-        #
-        #     name = filename.split('/')[-1].split('\\')[-1]
-        #     app_obj.new_object("gerber", name, obj_init)
-        #     app_obj.register_recent("gerber", filename)
-        #
-        #     GLib.idle_add(lambda: app_obj.set_progress_bar(1.0, "Done!"))
-        #     GLib.timeout_add_seconds(1, lambda: app_obj.set_progress_bar(0.0, ""))
-
-        # on_success gets run on a separate thread
-        # self.file_chooser_action(on_success)
+
         self.file_chooser_action(lambda ao, filename: self.open_gerber(filename))
         self.file_chooser_action(lambda ao, filename: self.open_gerber(filename))
 
 
     def open_excellon(self, filename):
     def open_excellon(self, filename):
@@ -2104,6 +2028,7 @@ class App:
         self.new_object("excellon", name, obj_init)
         self.new_object("excellon", name, obj_init)
         self.register_recent("excellon", filename)
         self.register_recent("excellon", filename)
 
 
+        self.info("Opened: " + filename)
         GLib.idle_add(lambda: self.set_progress_bar(1.0, "Done!"))
         GLib.idle_add(lambda: self.set_progress_bar(1.0, "Done!"))
         GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, ""))
         GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, ""))
 
 
@@ -2116,28 +2041,7 @@ class App:
         :param param: Ignore
         :param param: Ignore
         :return: None
         :return: None
         """
         """
-        # IMPORTANT: on_success will run on a separate thread. Use
-        # GLib.idle_add(function, **kwargs) to launch actions that will
-        # updata the GUI.
-        # def on_success(app_obj, filename):
-        #     assert isinstance(app_obj, App)
-        #     GLib.idle_add(lambda: app_obj.set_progress_bar(0.1, "Opening Excellon ..."))
-        #
-        #     def obj_init(excellon_obj, app_obj):
-        #         GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Parsing ..."))
-        #         excellon_obj.parse_file(filename)
-        #         excellon_obj.create_geometry()
-        #         GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Plotting ..."))
-        #
-        #     name = filename.split('/')[-1].split('\\')[-1]
-        #     app_obj.new_object("excellon", name, obj_init)
-        #     self.register_recent("excellon", filename)
-        #
-        #     GLib.idle_add(lambda: app_obj.set_progress_bar(1.0, "Done!"))
-        #     GLib.timeout_add_seconds(1, lambda: app_obj.set_progress_bar(0.0, ""))
-
-        # on_success gets run on a separate thread
-        # self.file_chooser_action(on_success)
+
         self.file_chooser_action(lambda ao, filename: self.open_excellon(filename))
         self.file_chooser_action(lambda ao, filename: self.open_excellon(filename))
 
 
     def open_gcode(self, filename):
     def open_gcode(self, filename):
@@ -2168,6 +2072,7 @@ class App:
         self.new_object("cncjob", name, obj_init)
         self.new_object("cncjob", name, obj_init)
         self.register_recent("cncjob", filename)
         self.register_recent("cncjob", filename)
 
 
+        self.info("Opened: " + filename)
         GLib.idle_add(lambda: self.set_progress_bar(1.0, "Done!"))
         GLib.idle_add(lambda: self.set_progress_bar(1.0, "Done!"))
         GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, ""))
         GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, ""))
 
 
@@ -2180,43 +2085,7 @@ class App:
         :param param: Ignore
         :param param: Ignore
         :return: None
         :return: None
         """
         """
-        # IMPORTANT: on_success will run on a separate thread. Use
-        # GLib.idle_add(function, **kwargs) to launch actions that will
-        # updata the GUI.
-        # def on_success(app_obj, filename):
-        #     assert isinstance(app_obj, App)
-        #
-        #     def obj_init(job_obj, app_obj_):
-        #         """
-        #
-        #         :type app_obj_: App
-        #         """
-        #         assert isinstance(app_obj_, App)
-        #         GLib.idle_add(lambda: app_obj_.set_progress_bar(0.1, "Opening G-Code ..."))
-        #
-        #         f = open(filename)
-        #         gcode = f.read()
-        #         f.close()
-        #
-        #         job_obj.gcode = gcode
-        #
-        #         GLib.idle_add(lambda: app_obj_.set_progress_bar(0.2, "Parsing ..."))
-        #         job_obj.gcode_parse()
-        #
-        #         GLib.idle_add(lambda: app_obj_.set_progress_bar(0.6, "Creating geometry ..."))
-        #         job_obj.create_geometry()
-        #
-        #         GLib.idle_add(lambda: app_obj_.set_progress_bar(0.6, "Plotting ..."))
-        #
-        #     name = filename.split('/')[-1].split('\\')[-1]
-        #     app_obj.new_object("cncjob", name, obj_init)
-        #     self.register_recent("cncjob", filename)
-        #
-        #     GLib.idle_add(lambda: app_obj.set_progress_bar(1.0, "Done!"))
-        #     GLib.timeout_add_seconds(1, lambda: app_obj.set_progress_bar(0.0, ""))
-
-        # on_success gets run on a separate thread
-        # self.file_chooser_action(on_success)
+
         self.file_chooser_action(lambda ao, filename: self.open_gcode(filename))
         self.file_chooser_action(lambda ao, filename: self.open_gcode(filename))
 
 
     def on_mouse_move_over_plot(self, event):
     def on_mouse_move_over_plot(self, event):
@@ -2234,8 +2103,8 @@ class App:
                 event.xdata, event.ydata))
                 event.xdata, event.ydata))
             self.mouse = [event.xdata, event.ydata]
             self.mouse = [event.xdata, event.ydata]
 
 
-            for subscriber in self.plot_mousemove_subscribers:
-                self.plot_mousemove_subscribers[subscriber](event)
+            # for subscriber in self.plot_mousemove_subscribers:
+            #     self.plot_mousemove_subscribers[subscriber](event)
 
 
         except:
         except:
             self.position_label.set_label("")
             self.position_label.set_label("")
@@ -2256,7 +2125,7 @@ class App:
         :return: None
         :return: None
         """
         """
 
 
-        # For key presses
+        # So it can receive key presses
         self.plotcanvas.canvas.grab_focus()
         self.plotcanvas.canvas.grab_focus()
 
 
         try:
         try:
@@ -2264,8 +2133,8 @@ class App:
                 event.button, event.x, event.y, event.xdata, event.ydata)
                 event.button, event.x, event.y, event.xdata, event.ydata)
 
 
             # TODO: This custom subscription mechanism is probably not necessary.
             # TODO: This custom subscription mechanism is probably not necessary.
-            for subscriber in self.plot_click_subscribers:
-                self.plot_click_subscribers[subscriber](event)
+            # for subscriber in self.plot_click_subscribers:
+            #     self.plot_click_subscribers[subscriber](event)
 
 
             self.clipboard.set_text("(%.4f, %.4f)" % (event.xdata, event.ydata), -1)
             self.clipboard.set_text("(%.4f, %.4f)" % (event.xdata, event.ydata), -1)
 
 
@@ -2403,31 +2272,32 @@ class DrawingPoint(DrawingObject):
 
 
 
 
 class Measurement:
 class Measurement:
-    def __init__(self, container, axes, click_subscibers, move_subscribers, update=None):
+    def __init__(self, container, plotcanvas, update=None):
         self.update = update
         self.update = update
         self.container = container
         self.container = container
         self.frame = None
         self.frame = None
         self.label = None
         self.label = None
-        self.click_subscribers = click_subscibers
-        self.move_subscribers = move_subscribers
         self.point1 = None
         self.point1 = None
         self.point2 = None
         self.point2 = None
         self.active = False
         self.active = False
+        self.plotcanvas = plotcanvas
+        self.click_subscription = None
+        self.move_subscription = None
 
 
     def toggle_active(self, *args):
     def toggle_active(self, *args):
         if self.active:  # Deactivate
         if self.active:  # Deactivate
             self.active = False
             self.active = False
-            self.move_subscribers.pop("meas")
-            self.click_subscribers.pop("meas")
             self.container.remove(self.frame)
             self.container.remove(self.frame)
             if self.update is not None:
             if self.update is not None:
                 self.update()
                 self.update()
+            self.plotcanvas.mpl_disconnect(self.click_subscription)
+            self.plotcanvas.mpl_disconnect(self.move_subscription)
             return False
             return False
         else:  # Activate
         else:  # Activate
             print "DEBUG: Activating Measurement Tool..."
             print "DEBUG: Activating Measurement Tool..."
             self.active = True
             self.active = True
-            self.click_subscribers["meas"] = self.on_click
-            self.move_subscribers["meas"] = self.on_move
+            self.click_subscription = self.plotcanvas.mpl_connect("button_press_event", self.on_click)
+            self.move_subscription = self.plotcanvas.mpl_connect('motion_notify_event', self.on_move)
             self.frame = Gtk.Frame()
             self.frame = Gtk.Frame()
             self.frame.set_margin_right(5)
             self.frame.set_margin_right(5)
             self.frame.set_margin_top(3)
             self.frame.set_margin_top(3)
@@ -2449,10 +2319,13 @@ class Measurement:
         if self.point1 is None:
         if self.point1 is None:
             self.label.set_label("Click on a reference point...")
             self.label.set_label("Click on a reference point...")
         else:
         else:
-            dx = event.xdata - self.point1[0]
-            dy = event.ydata - self.point1[1]
-            d = sqrt(dx**2 + dy**2)
-            self.label.set_label("D = %.4f  D(x) = %.4f  D(y) = %.4f" % (d, dx, dy))
+            try:
+                dx = event.xdata - self.point1[0]
+                dy = event.ydata - self.point1[1]
+                d = sqrt(dx**2 + dy**2)
+                self.label.set_label("D = %.4f  D(x) = %.4f  D(y) = %.4f" % (d, dx, dy))
+            except TypeError:
+                pass
         if self.update is not None:
         if self.update is not None:
             self.update()
             self.update()
 
 
@@ -2540,9 +2413,18 @@ class PlotCanvas:
         :type event_name: str
         :type event_name: str
         :param callback: Function to call
         :param callback: Function to call
         :type callback: func
         :type callback: func
-        :return: Nothing
+        :return: Connection id
+        :rtype: int
         """
         """
-        self.canvas.mpl_connect(event_name, callback)
+        return self.canvas.mpl_connect(event_name, callback)
+
+    def mpl_disconnect(self, cid):
+        """
+        Disconnect callback with the give id.
+        :param cid: Callback id.
+        :return: None
+        """
+        self.canvas.mpl_disconnect(cid)
 
 
     def connect(self, event_name, callback):
     def connect(self, event_name, callback):
         """
         """
@@ -2592,6 +2474,8 @@ class PlotCanvas:
         :return: None
         :return: None
         """
         """
 
 
+        print "PC.adjust_axes()"
+
         width = xmax - xmin
         width = xmax - xmin
         height = ymax - ymin
         height = ymax - ymin
         try:
         try:
@@ -2707,21 +2591,6 @@ class PlotCanvas:
 
 
         return self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label=name)
         return self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label=name)
 
 
-    # def plot_axes(self, axes):
-    #
-    #     if axes not in self.figure.axes:
-    #         self.figure.add_axes(axes)
-    #
-    #     # Basic configuration
-    #     axes.set_frame_on(False)  # No frame
-    #     axes.set_xticks([])  # No tick
-    #     axes.set_yticks([])  # No ticks
-    #     axes.patch.set_visible(False)  # No background
-    #     axes.set_aspect(1)
-    #
-    #     # Adjust limits
-    #     self.auto_adjust_axes()
-
     def on_scroll(self, canvas, event):
     def on_scroll(self, canvas, event):
         """
         """
         Scroll event handler.
         Scroll event handler.
@@ -2730,6 +2599,11 @@ class PlotCanvas:
         :param event: Event object containing the event information.
         :param event: Event object containing the event information.
         :return: None
         :return: None
         """
         """
+
+        # So it can receive key presses
+        self.canvas.grab_focus()
+
+        # Event info
         z, direction = event.get_scroll_direction()
         z, direction = event.get_scroll_direction()
 
 
         if self.key is None:
         if self.key is None:
@@ -2758,7 +2632,7 @@ class PlotCanvas:
 
 
     def on_mouse_move(self, event):
     def on_mouse_move(self, event):
         """
         """
-        Mouse movement event hadler.
+        Mouse movement event hadler. Stores the coordinates.
 
 
         :param event: Contains information about the event.
         :param event: Contains information about the event.
         :return: None
         :return: None
@@ -2822,6 +2696,13 @@ class ObjectCollection:
         column_text.set_cell_data_func(renderer_text, _set_cell_text)
         column_text.set_cell_data_func(renderer_text, _set_cell_text)
         self.view.append_column(column_text)
         self.view.append_column(column_text)
 
 
+    def print_list(self):
+        iterat = self.store.get_iter_first()
+        while iterat is not None:
+            obj = self.store[iterat][0]
+            print obj
+            iterat = self.store.iter_next(iterat)
+
     def delete_all(self):
     def delete_all(self):
         print "OC.delete_all()"
         print "OC.delete_all()"
         # self.collection = []
         # self.collection = []
@@ -2837,10 +2718,22 @@ class ObjectCollection:
             pass
             pass
 
 
     def on_row_activated(self, *args):
     def on_row_activated(self, *args):
+        """
+        Does nothing right now.
+        :param args: Ignored.
+        :return: None
+        """
         print "OC.on_row_activated()"
         print "OC.on_row_activated()"
         return
         return
 
 
     def on_list_selection_change(self, selection):
     def on_list_selection_change(self, selection):
+        """
+        Callback for change in selection on the objects' list.
+        Instructs the new selection to build the UI for its options.
+
+        :param selection: Ignored.
+        :return: None
+        """
         print "OC.on_list_selection_change()"
         print "OC.on_list_selection_change()"
         try:
         try:
             self.get_active().build_ui()
             self.get_active().build_ui()
@@ -2851,6 +2744,14 @@ class ObjectCollection:
         # TODO: active, so cannot read form.
         # TODO: active, so cannot read form.
 
 
     def set_active(self, name):
     def set_active(self, name):
+        """
+        Sets an object as the active object in the program. Same
+        as `set_list_selection()`.
+
+        :param name: Name of the object.
+        :type name: str
+        :return: None
+        """
         print "OC.set_active()"
         print "OC.set_active()"
         self.set_list_selection(name)
         self.set_list_selection(name)
 
 
@@ -2863,6 +2764,13 @@ class ObjectCollection:
             return None
             return None
 
 
     def set_list_selection(self, name):
     def set_list_selection(self, name):
+        """
+        Sets which object should be selected in the list.
+
+        :param name: Name of the object.
+        :rtype name: str
+        :return: None
+        """
         print "OC.set_list_selection()"
         print "OC.set_list_selection()"
         iterat = self.store.get_iter_first()
         iterat = self.store.get_iter_first()
         while iterat is not None and self.store[iterat][0].options["name"] != name:
         while iterat is not None and self.store[iterat][0].options["name"] != name:
@@ -2870,13 +2778,30 @@ class ObjectCollection:
         self.tree_selection.select_iter(iterat)
         self.tree_selection.select_iter(iterat)
 
 
     def append(self, obj, active=False):
     def append(self, obj, active=False):
+        """
+        Add a FlatCAMObj the the collection. This method is thread-safe.
+
+        :param obj: FlatCAMObj to append
+        :type obj: FlatCAMObj
+        :param active: If it is to become the active object after appending
+        :type active: bool
+        :return: None
+        """
         print "OC.append()"
         print "OC.append()"
 
 
-        self.store.append([obj])
-        if active:
-            self.set_list_selection(obj.options["name"])
+        def guitask():
+            self.store.append([obj])
+            if active:
+                self.set_list_selection(obj.options["name"])
+        GLib.idle_add(guitask)
 
 
     def get_names(self):
     def get_names(self):
+        """
+        Gets a list of the names of all objects in the collection.
+
+        :return: List of names.
+        :rtype: list
+        """
         print "OC.get_names()"
         print "OC.get_names()"
         names = []
         names = []
         iterat = self.store.get_iter_first()
         iterat = self.store.get_iter_first()
@@ -2887,6 +2812,12 @@ class ObjectCollection:
         return names
         return names
 
 
     def get_bounds(self):
     def get_bounds(self):
+        """
+        Finds coordinates bounding all objects in the collection.
+
+        :return: [xmin, ymin, xmax, ymax]
+        :rtype: list
+        """
         print "OC.get_bounds()"
         print "OC.get_bounds()"
 
 
         # TODO: Move the operation out of here.
         # TODO: Move the operation out of here.
@@ -2911,6 +2842,12 @@ class ObjectCollection:
         return [xmin, ymin, xmax, ymax]
         return [xmin, ymin, xmax, ymax]
 
 
     def get_list(self):
     def get_list(self):
+        """
+        Returns a list with all FlatCAMObj.
+
+        :return: List with all FlatCAMObj.
+        :rtype: list
+        """
         collection_list = []
         collection_list = []
         iterat = self.store.get_iter_first()
         iterat = self.store.get_iter_first()
         while iterat is not None:
         while iterat is not None:
@@ -2920,6 +2857,14 @@ class ObjectCollection:
         return collection_list
         return collection_list
 
 
     def get_by_name(self, name):
     def get_by_name(self, name):
+        """
+        Fetches the FlatCAMObj with the given `name`.
+
+        :param name: The name of the object.
+        :type name: str
+        :return: The requested object or None if no such object.
+        :rtype: FlatCAMObj or None
+        """
         iterat = self.store.get_iter_first()
         iterat = self.store.get_iter_first()
         while iterat is not None:
         while iterat is not None:
             obj = self.store[iterat][0]
             obj = self.store[iterat][0]
@@ -2929,6 +2874,17 @@ class ObjectCollection:
         return None
         return None
 
 
     def change_name(self, old_name, new_name):
     def change_name(self, old_name, new_name):
+        """
+        Changes the name of `FlatCAMObj` named `old_name` to `new_name`.
+
+        :param old_name: Name of the object to change.
+        :type old_name: str
+        :param new_name: New name.
+        :type new_name: str
+        :return: True if name change succeeded, False otherwise. Will fail
+           if no object with `old_name` is found.
+        :rtype: bool
+        """
         iterat = self.store.get_iter_first()
         iterat = self.store.get_iter_first()
         while iterat is not None:
         while iterat is not None:
             obj = self.store[iterat][0]
             obj = self.store[iterat][0]

+ 92 - 0
FlatCAM.ui

@@ -97,6 +97,11 @@ THE SOFTWARE.</property>
     <property name="can_focus">False</property>
     <property name="can_focus">False</property>
     <property name="stock">gtk-open</property>
     <property name="stock">gtk-open</property>
   </object>
   </object>
+  <object class="GtkImage" id="image21">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="pixbuf">share/bug16.png</property>
+  </object>
   <object class="GtkImage" id="image3">
   <object class="GtkImage" id="image3">
     <property name="visible">True</property>
     <property name="visible">True</property>
     <property name="can_focus">False</property>
     <property name="can_focus">False</property>
@@ -132,6 +137,77 @@ THE SOFTWARE.</property>
     <property name="can_focus">False</property>
     <property name="can_focus">False</property>
     <property name="stock">gtk-open</property>
     <property name="stock">gtk-open</property>
   </object>
   </object>
+  <object class="GtkDialog" id="question_dialog">
+    <property name="can_focus">False</property>
+    <property name="border_width">5</property>
+    <property name="type_hint">dialog</property>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="question_box">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">2</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox" id="dialog-action_area1">
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+            <child>
+              <placeholder/>
+            </child>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="box40">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkImage" id="image22">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="valign">start</property>
+                <property name="pixbuf">share/warning.png</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="label_warning">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_left">12</property>
+                <property name="margin_right">12</property>
+                <property name="margin_top">12</property>
+                <property name="margin_bottom">12</property>
+                <property name="hexpand">True</property>
+                <property name="label" translatable="yes">label</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
   <object class="GtkOffscreenWindow" id="offscreenwindow_dblsided">
   <object class="GtkOffscreenWindow" id="offscreenwindow_dblsided">
     <property name="can_focus">False</property>
     <property name="can_focus">False</property>
     <child>
     <child>
@@ -3488,6 +3564,22 @@ to application defaults.</property>
                         <signal name="activate" handler="on_tools_doublesided" swapped="no"/>
                         <signal name="activate" handler="on_tools_doublesided" swapped="no"/>
                       </object>
                       </object>
                     </child>
                     </child>
+                    <child>
+                      <object class="GtkSeparatorMenuItem" id="separatormenuitem7">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkImageMenuItem" id="imagemenuitem17">
+                        <property name="label" translatable="yes">List objects</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="image">image21</property>
+                        <property name="use_stock">False</property>
+                        <signal name="activate" handler="on_debug_printlist" swapped="no"/>
+                      </object>
+                    </child>
                   </object>
                   </object>
                 </child>
                 </child>
               </object>
               </object>

+ 3 - 0
FlatCAMObj.py

@@ -38,6 +38,9 @@ class FlatCAMObj(GObject.GObject, object):
         self.axes = None  # Matplotlib axes
         self.axes = None  # Matplotlib axes
         self.kind = None  # Override with proper name
         self.kind = None  # Override with proper name
 
 
+    def __str__(self):
+        return "<FlatCAMObj({:12s}): {:20s}>".format(self.kind, self.options["name"])
+
     def setup_axes(self, figure):
     def setup_axes(self, figure):
         """
         """
         1) Creates axes if they don't exist. 2) Clears axes. 3) Attaches
         1) Creates axes if they don't exist. 2) Clears axes. 3) Attaches

+ 7 - 2
camlib.py

@@ -1485,7 +1485,11 @@ class Excellon(Geometry):
 
 
         # Tool definition/parameters (?= is look-ahead
         # Tool definition/parameters (?= is look-ahead
         # NOTE: This might be an overkill!
         # NOTE: This might be an overkill!
-        self.toolset_re = re.compile(r'^T(0?\d|\d\d)(?=.*C(\d*\.?\d*))?' +
+        # self.toolset_re = re.compile(r'^T(0?\d|\d\d)(?=.*C(\d*\.?\d*))?' +
+        #                              r'(?=.*F(\d*\.?\d*))?(?=.*S(\d*\.?\d*))?' +
+        #                              r'(?=.*B(\d*\.?\d*))?(?=.*H(\d*\.?\d*))?' +
+        #                              r'(?=.*Z([-\+]?\d*\.?\d*))?[CFSBHT]')
+        self.toolset_re = re.compile(r'^T(\d+)(?=.*C(\d*\.?\d*))?' +
                                      r'(?=.*F(\d*\.?\d*))?(?=.*S(\d*\.?\d*))?' +
                                      r'(?=.*F(\d*\.?\d*))?(?=.*S(\d*\.?\d*))?' +
                                      r'(?=.*B(\d*\.?\d*))?(?=.*H(\d*\.?\d*))?' +
                                      r'(?=.*B(\d*\.?\d*))?(?=.*H(\d*\.?\d*))?' +
                                      r'(?=.*Z([-\+]?\d*\.?\d*))?[CFSBHT]')
                                      r'(?=.*Z([-\+]?\d*\.?\d*))?[CFSBHT]')
@@ -1494,7 +1498,8 @@ class Excellon(Geometry):
         # Can have additional data after tool number but
         # Can have additional data after tool number but
         # is ignored if present in the header.
         # is ignored if present in the header.
         # Warning: This will match toolset_re too.
         # Warning: This will match toolset_re too.
-        self.toolsel_re = re.compile(r'^T((?:\d\d)|(?:\d))')
+        # self.toolsel_re = re.compile(r'^T((?:\d\d)|(?:\d))')
+        self.toolsel_re = re.compile(r'^T(\d+)')
 
 
         # Comment
         # Comment
         self.comm_re = re.compile(r'^;(.*)$')
         self.comm_re = re.compile(r'^;(.*)$')

+ 1 - 1
defaults.json

@@ -1 +1 @@
-{"gerber_noncopperrounded": false, "geometry_paintoverlap": 0.15, "geometry_plot": true, "excellon_feedrate": 5.0, "gerber_plot": true, "gerber_mergepolys": true, "excellon_drillz": -0.1, "geometry_feedrate": 3.0, "units": "IN", "excellon_travelz": 0.1, "gerber_multicolored": false, "gerber_solid": true, "gerber_isopasses": 1, "excellon_plot": true, "gerber_isotooldia": 0.016, "gerber_bboxmargin": 0.0, "cncjob_tooldia": 0.016, "geometry_travelz": 0.1, "gerber_cutoutmargin": 0.2, "excellon_solid": false, "geometry_paintmargin": 0.01, "geometry_cutz": -0.002, "geometry_cnctooldia": 0.016, "gerber_cutouttooldia": 0.07, "gerber_gaps": "4", "geometry_painttooldia": 0.0625, "cncjob_plot": true, "gerber_cutoutgapsize": 0.15, "gerber_isooverlap": 0.15, "gerber_bboxrounded": false, "geometry_multicolored": false, "gerber_noncoppermargin": 0.0, "geometry_solid": false}
+{"gerber_noncopperrounded": false, "geometry_paintoverlap": 0.15, "geometry_plot": true, "excellon_feedrate": 5.0, "gerber_plot": true, "gerber_mergepolys": true, "excellon_drillz": -0.1, "geometry_feedrate": 3.0, "units": "IN", "excellon_travelz": 0.1, "gerber_multicolored": false, "gerber_solid": true, "gerber_isopasses": 1, "excellon_plot": true, "gerber_isotooldia": 0.016, "cncjob_tooldia": 0.016, "geometry_travelz": 0.1, "gerber_cutoutmargin": 0.2, "excellon_solid": false, "geometry_paintmargin": 0.01, "geometry_cutz": -0.002, "geometry_cnctooldia": 0.016, "gerber_cutouttooldia": 0.07, "geometry_painttooldia": 0.0625, "gerber_gaps": "4", "gerber_bboxmargin": 0.0, "cncjob_plot": true, "gerber_cutoutgapsize": 0.15, "gerber_isooverlap": 0.15, "gerber_bboxrounded": false, "geometry_multicolored": false, "gerber_noncoppermargin": 0.0, "geometry_solid": false}

BIN
doc/build/.doctrees/camlib.doctree


BIN
doc/build/.doctrees/environment.pickle


+ 74 - 13
doc/build/camlib.html

@@ -100,10 +100,14 @@
 <li class="toctree-l1"><a class="reference internal" href="app.html">FlatCAM Application</a><ul>
 <li class="toctree-l1"><a class="reference internal" href="app.html">FlatCAM Application</a><ul>
 <li class="toctree-l2"><a class="reference internal" href="app.html#app">App</a></li>
 <li class="toctree-l2"><a class="reference internal" href="app.html#app">App</a></li>
 <li class="toctree-l2"><a class="reference internal" href="app.html#plotcanvas">PlotCanvas</a></li>
 <li class="toctree-l2"><a class="reference internal" href="app.html#plotcanvas">PlotCanvas</a></li>
+<li class="toctree-l2"><a class="reference internal" href="app.html#objectcollection">ObjectCollection</a></li>
+<li class="toctree-l2"><a class="reference internal" href="app.html#measurement">Measurement</a></li>
 </ul>
 </ul>
 </li>
 </li>
 <li class="toctree-l1"><a class="reference internal" href="devman.html">FlatCAM Developer Manual</a><ul>
 <li class="toctree-l1"><a class="reference internal" href="devman.html">FlatCAM Developer Manual</a><ul>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#options">Options</a></li>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#options">Options</a></li>
+<li class="toctree-l2"><a class="reference internal" href="devman.html#serialization">Serialization</a></li>
+<li class="toctree-l2"><a class="reference internal" href="devman.html#geometry-processing">Geometry Processing</a></li>
 </ul>
 </ul>
 </li>
 </li>
 </ul>
 </ul>
@@ -523,7 +527,19 @@ box in both positive and negative, x and y axes.</li>
 <dl class="method">
 <dl class="method">
 <dt id="camlib.Gerber.mirror">
 <dt id="camlib.Gerber.mirror">
 <tt class="descname">mirror</tt><big>(</big><em>axis</em>, <em>point</em><big>)</big><a class="headerlink" href="#camlib.Gerber.mirror" title="Permalink to this definition">¶</a></dt>
 <tt class="descname">mirror</tt><big>(</big><em>axis</em>, <em>point</em><big>)</big><a class="headerlink" href="#camlib.Gerber.mirror" title="Permalink to this definition">¶</a></dt>
-<dd><table class="docutils field-list" frame="void" rules="none">
+<dd><p>Mirrors the object around a specified axis passign through
+the given point. What is affected:</p>
+<ul class="simple">
+<li><tt class="docutils literal"><span class="pre">buffered_paths</span></tt></li>
+<li><tt class="docutils literal"><span class="pre">flash_geometry</span></tt></li>
+<li><tt class="docutils literal"><span class="pre">solid_geometry</span></tt></li>
+<li><tt class="docutils literal"><span class="pre">regions</span></tt></li>
+</ul>
+<p>NOTE:
+Does not modify the data used to create these elements. If these
+are recreated, the scaling will be lost. This behavior was modified
+because of the complexity reached in this class.</p>
+<table class="docutils field-list" frame="void" rules="none">
 <col class="field-name" />
 <col class="field-name" />
 <col class="field-body" />
 <col class="field-body" />
 <tbody valign="top">
 <tbody valign="top">
@@ -546,12 +562,15 @@ box in both positive and negative, x and y axes.</li>
 <dd><p>Offsets the objects&#8217; geometry on the XY plane by a given vector.
 <dd><p>Offsets the objects&#8217; geometry on the XY plane by a given vector.
 These are:</p>
 These are:</p>
 <ul class="simple">
 <ul class="simple">
-<li><tt class="docutils literal"><span class="pre">paths</span></tt></li>
+<li><tt class="docutils literal"><span class="pre">buffered_paths</span></tt></li>
+<li><tt class="docutils literal"><span class="pre">flash_geometry</span></tt></li>
+<li><tt class="docutils literal"><span class="pre">solid_geometry</span></tt></li>
 <li><tt class="docutils literal"><span class="pre">regions</span></tt></li>
 <li><tt class="docutils literal"><span class="pre">regions</span></tt></li>
-<li><tt class="docutils literal"><span class="pre">flashes</span></tt></li>
 </ul>
 </ul>
-<p>Then <tt class="docutils literal"><span class="pre">buffered_paths</span></tt>, <tt class="docutils literal"><span class="pre">flash_geometry</span></tt> and <tt class="docutils literal"><span class="pre">solid_geometry</span></tt>
-are re-created with <tt class="docutils literal"><span class="pre">self.create_geometry()</span></tt>.</p>
+<p>NOTE:
+Does not modify the data used to create these elements. If these
+are recreated, the scaling will be lost. This behavior was modified
+because of the complexity reached in this class.</p>
 <table class="docutils field-list" frame="void" rules="none">
 <table class="docutils field-list" frame="void" rules="none">
 <col class="field-name" />
 <col class="field-name" />
 <col class="field-body" />
 <col class="field-body" />
@@ -607,16 +626,24 @@ one line of the source file.</td>
 <dd><p>Scales the objects&#8217; geometry on the XY plane by a given factor.
 <dd><p>Scales the objects&#8217; geometry on the XY plane by a given factor.
 These are:</p>
 These are:</p>
 <ul class="simple">
 <ul class="simple">
-<li><tt class="docutils literal"><span class="pre">apertures</span></tt></li>
-<li><tt class="docutils literal"><span class="pre">paths</span></tt></li>
+<li><tt class="docutils literal"><span class="pre">buffered_paths</span></tt></li>
+<li><tt class="docutils literal"><span class="pre">flash_geometry</span></tt></li>
+<li><tt class="docutils literal"><span class="pre">solid_geometry</span></tt></li>
 <li><tt class="docutils literal"><span class="pre">regions</span></tt></li>
 <li><tt class="docutils literal"><span class="pre">regions</span></tt></li>
-<li><tt class="docutils literal"><span class="pre">flashes</span></tt></li>
 </ul>
 </ul>
-<p>Then <tt class="docutils literal"><span class="pre">buffered_paths</span></tt>, <tt class="docutils literal"><span class="pre">flash_geometry</span></tt> and <tt class="docutils literal"><span class="pre">solid_geometry</span></tt>
-are re-created with <tt class="docutils literal"><span class="pre">self.create_geometry()</span></tt>.
-:param factor: Number by which to scale.
-:type factor: float
-:rtype : None</p>
+<p>NOTE:
+Does not modify the data used to create these elements. If these
+are recreated, the scaling will be lost. This behavior was modified
+because of the complexity reached in this class.</p>
+<table class="docutils field-list" frame="void" rules="none">
+<col class="field-name" />
+<col class="field-body" />
+<tbody valign="top">
+<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><strong>factor</strong> (<em>float</em>) &#8211; Number by which to scale.</td>
+</tr>
+</tbody>
+</table>
+<p>:rtype : None</p>
 </dd></dl>
 </dd></dl>
 
 
 </dd></dl>
 </dd></dl>
@@ -668,6 +695,23 @@ list of length n.</p>
 </table>
 </table>
 </dd></dl>
 </dd></dl>
 
 
+<dl class="method">
+<dt id="camlib.ApertureMacro.from_dict">
+<tt class="descname">from_dict</tt><big>(</big><em>d</em><big>)</big><a class="headerlink" href="#camlib.ApertureMacro.from_dict" title="Permalink to this definition">¶</a></dt>
+<dd><p>Populates the object from a serial representation created
+with <tt class="docutils literal"><span class="pre">self.to_dict()</span></tt>.</p>
+<table class="docutils field-list" frame="void" rules="none">
+<col class="field-name" />
+<col class="field-body" />
+<tbody valign="top">
+<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><strong>d</strong> &#8211; Serial representation of an ApertureMacro object.</td>
+</tr>
+<tr class="field-even field"><th class="field-name">Returns:</th><td class="field-body">None</td>
+</tr>
+</tbody>
+</table>
+</dd></dl>
+
 <dl class="staticmethod">
 <dl class="staticmethod">
 <dt id="camlib.ApertureMacro.make_centerline">
 <dt id="camlib.ApertureMacro.make_centerline">
 <em class="property">static </em><tt class="descname">make_centerline</tt><big>(</big><em>mods</em><big>)</big><a class="headerlink" href="#camlib.ApertureMacro.make_centerline" title="Permalink to this definition">¶</a></dt>
 <em class="property">static </em><tt class="descname">make_centerline</tt><big>(</big><em>mods</em><big>)</big><a class="headerlink" href="#camlib.ApertureMacro.make_centerline" title="Permalink to this definition">¶</a></dt>
@@ -833,6 +877,23 @@ are stored in <tt class="docutils literal"><span class="pre">self.primitives</sp
 </table>
 </table>
 </dd></dl>
 </dd></dl>
 
 
+<dl class="method">
+<dt id="camlib.ApertureMacro.to_dict">
+<tt class="descname">to_dict</tt><big>(</big><big>)</big><a class="headerlink" href="#camlib.ApertureMacro.to_dict" title="Permalink to this definition">¶</a></dt>
+<dd><p>Returns the object in a serializable form. Only the name and
+raw are required.</p>
+<table class="docutils field-list" frame="void" rules="none">
+<col class="field-name" />
+<col class="field-body" />
+<tbody valign="top">
+<tr class="field-odd field"><th class="field-name">Returns:</th><td class="field-body">Dictionary representing the object. JSON ready.</td>
+</tr>
+<tr class="field-even field"><th class="field-name">Return type:</th><td class="field-body">dict</td>
+</tr>
+</tbody>
+</table>
+</dd></dl>
+
 </dd></dl>
 </dd></dl>
 
 
 </div>
 </div>

+ 88 - 25
doc/build/genindex.html

@@ -99,11 +99,14 @@
 <li class="toctree-l1"><a class="reference internal" href="app.html">FlatCAM Application</a><ul>
 <li class="toctree-l1"><a class="reference internal" href="app.html">FlatCAM Application</a><ul>
 <li class="toctree-l2"><a class="reference internal" href="app.html#app">App</a></li>
 <li class="toctree-l2"><a class="reference internal" href="app.html#app">App</a></li>
 <li class="toctree-l2"><a class="reference internal" href="app.html#plotcanvas">PlotCanvas</a></li>
 <li class="toctree-l2"><a class="reference internal" href="app.html#plotcanvas">PlotCanvas</a></li>
+<li class="toctree-l2"><a class="reference internal" href="app.html#objectcollection">ObjectCollection</a></li>
+<li class="toctree-l2"><a class="reference internal" href="app.html#measurement">Measurement</a></li>
 </ul>
 </ul>
 </li>
 </li>
 <li class="toctree-l1"><a class="reference internal" href="devman.html">FlatCAM Developer Manual</a><ul>
 <li class="toctree-l1"><a class="reference internal" href="devman.html">FlatCAM Developer Manual</a><ul>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#options">Options</a></li>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#options">Options</a></li>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#serialization">Serialization</a></li>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#serialization">Serialization</a></li>
+<li class="toctree-l2"><a class="reference internal" href="devman.html#geometry-processing">Geometry Processing</a></li>
 </ul>
 </ul>
 </li>
 </li>
 </ul>
 </ul>
@@ -187,6 +190,12 @@
   <dt><a href="camlib.html#camlib.ApertureMacro.append">append() (camlib.ApertureMacro method)</a>
   <dt><a href="camlib.html#camlib.ApertureMacro.append">append() (camlib.ApertureMacro method)</a>
   </dt>
   </dt>
 
 
+      <dd><dl>
+        
+  <dt><a href="app.html#FlatCAM.ObjectCollection.append">(FlatCAM.ObjectCollection method)</a>
+  </dt>
+
+      </dl></dd>
       
       
   <dt><a href="app.html#FlatCAM.PlotCanvas.auto_adjust_axes">auto_adjust_axes() (FlatCAM.PlotCanvas method)</a>
   <dt><a href="app.html#FlatCAM.PlotCanvas.auto_adjust_axes">auto_adjust_axes() (FlatCAM.PlotCanvas method)</a>
   </dt>
   </dt>
@@ -208,10 +217,6 @@
   </dl></td>
   </dl></td>
   <td style="width: 33%" valign="top"><dl>
   <td style="width: 33%" valign="top"><dl>
       
       
-  <dt><a href="app.html#FlatCAM.App.build_list">build_list() (FlatCAM.App method)</a>
-  </dt>
-
-      
   <dt><a href="flatcamobj.html#FlatCAM.FlatCAMObj.build_ui">build_ui() (FlatCAM.FlatCAMObj method)</a>
   <dt><a href="flatcamobj.html#FlatCAM.FlatCAMObj.build_ui">build_ui() (FlatCAM.FlatCAMObj method)</a>
   </dt>
   </dt>
 
 
@@ -226,6 +231,10 @@
   </dt>
   </dt>
 
 
       
       
+  <dt><a href="app.html#FlatCAM.ObjectCollection.change_name">change_name() (FlatCAM.ObjectCollection method)</a>
+  </dt>
+
+      
   <dt><a href="app.html#FlatCAM.PlotCanvas.clear">clear() (FlatCAM.PlotCanvas method)</a>
   <dt><a href="app.html#FlatCAM.PlotCanvas.clear">clear() (FlatCAM.PlotCanvas method)</a>
   </dt>
   </dt>
 
 
@@ -233,12 +242,12 @@
   <dt><a href="camlib.html#camlib.Geometry.clear_polygon">clear_polygon() (camlib.Geometry method)</a>
   <dt><a href="camlib.html#camlib.Geometry.clear_polygon">clear_polygon() (camlib.Geometry method)</a>
   </dt>
   </dt>
 
 
+  </dl></td>
+  <td style="width: 33%" valign="top"><dl>
       
       
   <dt><a href="camlib.html#camlib.CNCjob">CNCjob (class in camlib)</a>
   <dt><a href="camlib.html#camlib.CNCjob">CNCjob (class in camlib)</a>
   </dt>
   </dt>
 
 
-  </dl></td>
-  <td style="width: 33%" valign="top"><dl>
       
       
   <dt><a href="app.html#FlatCAM.PlotCanvas.connect">connect() (FlatCAM.PlotCanvas method)</a>
   <dt><a href="app.html#FlatCAM.PlotCanvas.connect">connect() (FlatCAM.PlotCanvas method)</a>
   </dt>
   </dt>
@@ -280,6 +289,10 @@
   </dl></td>
   </dl></td>
   <td style="width: 33%" valign="top"><dl>
   <td style="width: 33%" valign="top"><dl>
       
       
+  <dt><a href="app.html#FlatCAM.App.disable_plots">disable_plots() (FlatCAM.App method)</a>
+  </dt>
+
+      
   <dt><a href="camlib.html#camlib.Gerber.do_flashes">do_flashes() (camlib.Gerber method)</a>
   <dt><a href="camlib.html#camlib.Gerber.do_flashes">do_flashes() (camlib.Gerber method)</a>
   </dt>
   </dt>
 
 
@@ -342,9 +355,15 @@
   </dt>
   </dt>
 
 
       
       
-  <dt><a href="camlib.html#camlib.Geometry.from_dict">from_dict() (camlib.Geometry method)</a>
+  <dt><a href="camlib.html#camlib.ApertureMacro.from_dict">from_dict() (camlib.ApertureMacro method)</a>
+  </dt>
+
+      <dd><dl>
+        
+  <dt><a href="camlib.html#camlib.Geometry.from_dict">(camlib.Geometry method)</a>
   </dt>
   </dt>
 
 
+      </dl></dd>
   </dl></td>
   </dl></td>
 </tr></table>
 </tr></table>
 
 
@@ -375,14 +394,18 @@
   <dt><a href="camlib.html#camlib.Gerber">Gerber (class in camlib)</a>
   <dt><a href="camlib.html#camlib.Gerber">Gerber (class in camlib)</a>
   </dt>
   </dt>
 
 
+      
+  <dt><a href="camlib.html#camlib.Gerber.get_bounding_box">get_bounding_box() (camlib.Gerber method)</a>
+  </dt>
+
   </dl></td>
   </dl></td>
   <td style="width: 33%" valign="top"><dl>
   <td style="width: 33%" valign="top"><dl>
       
       
-  <dt><a href="camlib.html#camlib.Gerber.get_bounding_box">get_bounding_box() (camlib.Gerber method)</a>
+  <dt><a href="app.html#FlatCAM.ObjectCollection.get_bounds">get_bounds() (FlatCAM.ObjectCollection method)</a>
   </dt>
   </dt>
 
 
       
       
-  <dt><a href="app.html#FlatCAM.App.get_current">get_current() (FlatCAM.App method)</a>
+  <dt><a href="app.html#FlatCAM.ObjectCollection.get_by_name">get_by_name() (FlatCAM.ObjectCollection method)</a>
   </dt>
   </dt>
 
 
       
       
@@ -394,6 +417,14 @@
   </dt>
   </dt>
 
 
       
       
+  <dt><a href="app.html#FlatCAM.ObjectCollection.get_list">get_list() (FlatCAM.ObjectCollection method)</a>
+  </dt>
+
+      
+  <dt><a href="app.html#FlatCAM.ObjectCollection.get_names">get_names() (FlatCAM.ObjectCollection method)</a>
+  </dt>
+
+      
   <dt><a href="app.html#FlatCAM.App.get_radio_value">get_radio_value() (FlatCAM.App method)</a>
   <dt><a href="app.html#FlatCAM.App.get_radio_value">get_radio_value() (FlatCAM.App method)</a>
   </dt>
   </dt>
 
 
@@ -463,12 +494,12 @@
   <dt><a href="camlib.html#camlib.ApertureMacro.make_outline">make_outline() (camlib.ApertureMacro static method)</a>
   <dt><a href="camlib.html#camlib.ApertureMacro.make_outline">make_outline() (camlib.ApertureMacro static method)</a>
   </dt>
   </dt>
 
 
-  </dl></td>
-  <td style="width: 33%" valign="top"><dl>
       
       
   <dt><a href="camlib.html#camlib.ApertureMacro.make_polygon">make_polygon() (camlib.ApertureMacro static method)</a>
   <dt><a href="camlib.html#camlib.ApertureMacro.make_polygon">make_polygon() (camlib.ApertureMacro static method)</a>
   </dt>
   </dt>
 
 
+  </dl></td>
+  <td style="width: 33%" valign="top"><dl>
       
       
   <dt><a href="camlib.html#camlib.ApertureMacro.make_thermal">make_thermal() (camlib.ApertureMacro static method)</a>
   <dt><a href="camlib.html#camlib.ApertureMacro.make_thermal">make_thermal() (camlib.ApertureMacro static method)</a>
   </dt>
   </dt>
@@ -478,6 +509,10 @@
   </dt>
   </dt>
 
 
       
       
+  <dt><a href="app.html#FlatCAM.Measurement">Measurement (class in FlatCAM)</a>
+  </dt>
+
+      
   <dt><a href="camlib.html#camlib.Excellon.mirror">mirror() (camlib.Excellon method)</a>
   <dt><a href="camlib.html#camlib.Excellon.mirror">mirror() (camlib.Excellon method)</a>
   </dt>
   </dt>
 
 
@@ -491,6 +526,10 @@
   <dt><a href="app.html#FlatCAM.PlotCanvas.mpl_connect">mpl_connect() (FlatCAM.PlotCanvas method)</a>
   <dt><a href="app.html#FlatCAM.PlotCanvas.mpl_connect">mpl_connect() (FlatCAM.PlotCanvas method)</a>
   </dt>
   </dt>
 
 
+      
+  <dt><a href="app.html#FlatCAM.PlotCanvas.mpl_disconnect">mpl_disconnect() (FlatCAM.PlotCanvas method)</a>
+  </dt>
+
   </dl></td>
   </dl></td>
 </tr></table>
 </tr></table>
 
 
@@ -514,6 +553,10 @@
 <table style="width: 100%" class="indextable genindextable"><tr>
 <table style="width: 100%" class="indextable genindextable"><tr>
   <td style="width: 33%" valign="top"><dl>
   <td style="width: 33%" valign="top"><dl>
       
       
+  <dt><a href="app.html#FlatCAM.ObjectCollection">ObjectCollection (class in FlatCAM)</a>
+  </dt>
+
+      
   <dt><a href="camlib.html#camlib.CNCjob.offset">offset() (camlib.CNCjob method)</a>
   <dt><a href="camlib.html#camlib.CNCjob.offset">offset() (camlib.CNCjob method)</a>
   </dt>
   </dt>
 
 
@@ -647,12 +690,12 @@
   <dt><a href="app.html#FlatCAM.App.on_generate_isolation">on_generate_isolation() (FlatCAM.App method)</a>
   <dt><a href="app.html#FlatCAM.App.on_generate_isolation">on_generate_isolation() (FlatCAM.App method)</a>
   </dt>
   </dt>
 
 
-  </dl></td>
-  <td style="width: 33%" valign="top"><dl>
       
       
   <dt><a href="app.html#FlatCAM.App.on_generate_paintarea">on_generate_paintarea() (FlatCAM.App method)</a>
   <dt><a href="app.html#FlatCAM.App.on_generate_paintarea">on_generate_paintarea() (FlatCAM.App method)</a>
   </dt>
   </dt>
 
 
+  </dl></td>
+  <td style="width: 33%" valign="top"><dl>
       
       
   <dt><a href="app.html#FlatCAM.App.on_gerber_generate_cutout">on_gerber_generate_cutout() (FlatCAM.App method)</a>
   <dt><a href="app.html#FlatCAM.App.on_gerber_generate_cutout">on_gerber_generate_cutout() (FlatCAM.App method)</a>
   </dt>
   </dt>
@@ -662,10 +705,22 @@
   </dt>
   </dt>
 
 
       
       
+  <dt><a href="app.html#FlatCAM.PlotCanvas.on_key_down">on_key_down() (FlatCAM.PlotCanvas method)</a>
+  </dt>
+
+      
   <dt><a href="app.html#FlatCAM.App.on_key_over_plot">on_key_over_plot() (FlatCAM.App method)</a>
   <dt><a href="app.html#FlatCAM.App.on_key_over_plot">on_key_over_plot() (FlatCAM.App method)</a>
   </dt>
   </dt>
 
 
       
       
+  <dt><a href="app.html#FlatCAM.PlotCanvas.on_key_up">on_key_up() (FlatCAM.PlotCanvas method)</a>
+  </dt>
+
+      
+  <dt><a href="app.html#FlatCAM.ObjectCollection.on_list_selection_change">on_list_selection_change() (FlatCAM.ObjectCollection method)</a>
+  </dt>
+
+      
   <dt><a href="app.html#FlatCAM.PlotCanvas.on_mouse_move">on_mouse_move() (FlatCAM.PlotCanvas method)</a>
   <dt><a href="app.html#FlatCAM.PlotCanvas.on_mouse_move">on_mouse_move() (FlatCAM.PlotCanvas method)</a>
   </dt>
   </dt>
 
 
@@ -713,6 +768,12 @@
   <dt><a href="app.html#FlatCAM.App.on_row_activated">on_row_activated() (FlatCAM.App method)</a>
   <dt><a href="app.html#FlatCAM.App.on_row_activated">on_row_activated() (FlatCAM.App method)</a>
   </dt>
   </dt>
 
 
+      <dd><dl>
+        
+  <dt><a href="app.html#FlatCAM.ObjectCollection.on_row_activated">(FlatCAM.ObjectCollection method)</a>
+  </dt>
+
+      </dl></dd>
       
       
   <dt><a href="app.html#FlatCAM.App.on_scale_object">on_scale_object() (FlatCAM.App method)</a>
   <dt><a href="app.html#FlatCAM.App.on_scale_object">on_scale_object() (FlatCAM.App method)</a>
   </dt>
   </dt>
@@ -738,10 +799,6 @@
   </dt>
   </dt>
 
 
       
       
-  <dt><a href="app.html#FlatCAM.App.on_tree_selection_changed">on_tree_selection_changed() (FlatCAM.App method)</a>
-  </dt>
-
-      
   <dt><a href="app.html#FlatCAM.App.on_update_plot">on_update_plot() (FlatCAM.App method)</a>
   <dt><a href="app.html#FlatCAM.App.on_update_plot">on_update_plot() (FlatCAM.App method)</a>
   </dt>
   </dt>
 
 
@@ -896,6 +953,10 @@
   </dt>
   </dt>
 
 
       
       
+  <dt><a href="app.html#FlatCAM.ObjectCollection.set_active">set_active() (FlatCAM.ObjectCollection method)</a>
+  </dt>
+
+      
   <dt><a href="app.html#FlatCAM.App.set_form_item">set_form_item() (FlatCAM.App method)</a>
   <dt><a href="app.html#FlatCAM.App.set_form_item">set_form_item() (FlatCAM.App method)</a>
   </dt>
   </dt>
 
 
@@ -906,15 +967,15 @@
 
 
       </dl></dd>
       </dl></dd>
       
       
-  <dt><a href="app.html#FlatCAM.App.set_list_selection">set_list_selection() (FlatCAM.App method)</a>
+  <dt><a href="app.html#FlatCAM.ObjectCollection.set_list_selection">set_list_selection() (FlatCAM.ObjectCollection method)</a>
   </dt>
   </dt>
 
 
+  </dl></td>
+  <td style="width: 33%" valign="top"><dl>
       
       
   <dt><a href="app.html#FlatCAM.App.set_progress_bar">set_progress_bar() (FlatCAM.App method)</a>
   <dt><a href="app.html#FlatCAM.App.set_progress_bar">set_progress_bar() (FlatCAM.App method)</a>
   </dt>
   </dt>
 
 
-  </dl></td>
-  <td style="width: 33%" valign="top"><dl>
       
       
   <dt><a href="flatcamobj.html#FlatCAM.FlatCAMObj.setup_axes">setup_axes() (FlatCAM.FlatCAMObj method)</a>
   <dt><a href="flatcamobj.html#FlatCAM.FlatCAMObj.setup_axes">setup_axes() (FlatCAM.FlatCAMObj method)</a>
   </dt>
   </dt>
@@ -928,10 +989,6 @@
   </dt>
   </dt>
 
 
       
       
-  <dt><a href="app.html#FlatCAM.App.setup_project_list">setup_project_list() (FlatCAM.App method)</a>
-  </dt>
-
-      
   <dt><a href="camlib.html#camlib.Geometry.size">size() (camlib.Geometry method)</a>
   <dt><a href="camlib.html#camlib.Geometry.size">size() (camlib.Geometry method)</a>
   </dt>
   </dt>
 
 
@@ -942,9 +999,15 @@
 <table style="width: 100%" class="indextable genindextable"><tr>
 <table style="width: 100%" class="indextable genindextable"><tr>
   <td style="width: 33%" valign="top"><dl>
   <td style="width: 33%" valign="top"><dl>
       
       
-  <dt><a href="camlib.html#camlib.Geometry.to_dict">to_dict() (camlib.Geometry method)</a>
+  <dt><a href="camlib.html#camlib.ApertureMacro.to_dict">to_dict() (camlib.ApertureMacro method)</a>
   </dt>
   </dt>
 
 
+      <dd><dl>
+        
+  <dt><a href="camlib.html#camlib.Geometry.to_dict">(camlib.Geometry method)</a>
+  </dt>
+
+      </dl></dd>
   </dl></td>
   </dl></td>
   <td style="width: 33%" valign="top"><dl>
   <td style="width: 33%" valign="top"><dl>
       
       

+ 6 - 0
doc/build/index.html

@@ -99,11 +99,14 @@
 <li class="toctree-l1"><a class="reference internal" href="app.html">FlatCAM Application</a><ul>
 <li class="toctree-l1"><a class="reference internal" href="app.html">FlatCAM Application</a><ul>
 <li class="toctree-l2"><a class="reference internal" href="app.html#app">App</a></li>
 <li class="toctree-l2"><a class="reference internal" href="app.html#app">App</a></li>
 <li class="toctree-l2"><a class="reference internal" href="app.html#plotcanvas">PlotCanvas</a></li>
 <li class="toctree-l2"><a class="reference internal" href="app.html#plotcanvas">PlotCanvas</a></li>
+<li class="toctree-l2"><a class="reference internal" href="app.html#objectcollection">ObjectCollection</a></li>
+<li class="toctree-l2"><a class="reference internal" href="app.html#measurement">Measurement</a></li>
 </ul>
 </ul>
 </li>
 </li>
 <li class="toctree-l1"><a class="reference internal" href="devman.html">FlatCAM Developer Manual</a><ul>
 <li class="toctree-l1"><a class="reference internal" href="devman.html">FlatCAM Developer Manual</a><ul>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#options">Options</a></li>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#options">Options</a></li>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#serialization">Serialization</a></li>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#serialization">Serialization</a></li>
+<li class="toctree-l2"><a class="reference internal" href="devman.html#geometry-processing">Geometry Processing</a></li>
 </ul>
 </ul>
 </li>
 </li>
 </ul>
 </ul>
@@ -164,11 +167,14 @@
 <li class="toctree-l1"><a class="reference internal" href="app.html">FlatCAM Application</a><ul>
 <li class="toctree-l1"><a class="reference internal" href="app.html">FlatCAM Application</a><ul>
 <li class="toctree-l2"><a class="reference internal" href="app.html#app">App</a></li>
 <li class="toctree-l2"><a class="reference internal" href="app.html#app">App</a></li>
 <li class="toctree-l2"><a class="reference internal" href="app.html#plotcanvas">PlotCanvas</a></li>
 <li class="toctree-l2"><a class="reference internal" href="app.html#plotcanvas">PlotCanvas</a></li>
+<li class="toctree-l2"><a class="reference internal" href="app.html#objectcollection">ObjectCollection</a></li>
+<li class="toctree-l2"><a class="reference internal" href="app.html#measurement">Measurement</a></li>
 </ul>
 </ul>
 </li>
 </li>
 <li class="toctree-l1"><a class="reference internal" href="devman.html">FlatCAM Developer Manual</a><ul>
 <li class="toctree-l1"><a class="reference internal" href="devman.html">FlatCAM Developer Manual</a><ul>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#options">Options</a></li>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#options">Options</a></li>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#serialization">Serialization</a></li>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#serialization">Serialization</a></li>
+<li class="toctree-l2"><a class="reference internal" href="devman.html#geometry-processing">Geometry Processing</a></li>
 </ul>
 </ul>
 </li>
 </li>
 </ul>
 </ul>

BIN
doc/build/objects.inv


+ 3 - 0
doc/build/py-modindex.html

@@ -105,11 +105,14 @@
 <li class="toctree-l1"><a class="reference internal" href="app.html">FlatCAM Application</a><ul>
 <li class="toctree-l1"><a class="reference internal" href="app.html">FlatCAM Application</a><ul>
 <li class="toctree-l2"><a class="reference internal" href="app.html#app">App</a></li>
 <li class="toctree-l2"><a class="reference internal" href="app.html#app">App</a></li>
 <li class="toctree-l2"><a class="reference internal" href="app.html#plotcanvas">PlotCanvas</a></li>
 <li class="toctree-l2"><a class="reference internal" href="app.html#plotcanvas">PlotCanvas</a></li>
+<li class="toctree-l2"><a class="reference internal" href="app.html#objectcollection">ObjectCollection</a></li>
+<li class="toctree-l2"><a class="reference internal" href="app.html#measurement">Measurement</a></li>
 </ul>
 </ul>
 </li>
 </li>
 <li class="toctree-l1"><a class="reference internal" href="devman.html">FlatCAM Developer Manual</a><ul>
 <li class="toctree-l1"><a class="reference internal" href="devman.html">FlatCAM Developer Manual</a><ul>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#options">Options</a></li>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#options">Options</a></li>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#serialization">Serialization</a></li>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#serialization">Serialization</a></li>
+<li class="toctree-l2"><a class="reference internal" href="devman.html#geometry-processing">Geometry Processing</a></li>
 </ul>
 </ul>
 </li>
 </li>
 </ul>
 </ul>

+ 3 - 0
doc/build/search.html

@@ -106,11 +106,14 @@
 <li class="toctree-l1"><a class="reference internal" href="app.html">FlatCAM Application</a><ul>
 <li class="toctree-l1"><a class="reference internal" href="app.html">FlatCAM Application</a><ul>
 <li class="toctree-l2"><a class="reference internal" href="app.html#app">App</a></li>
 <li class="toctree-l2"><a class="reference internal" href="app.html#app">App</a></li>
 <li class="toctree-l2"><a class="reference internal" href="app.html#plotcanvas">PlotCanvas</a></li>
 <li class="toctree-l2"><a class="reference internal" href="app.html#plotcanvas">PlotCanvas</a></li>
+<li class="toctree-l2"><a class="reference internal" href="app.html#objectcollection">ObjectCollection</a></li>
+<li class="toctree-l2"><a class="reference internal" href="app.html#measurement">Measurement</a></li>
 </ul>
 </ul>
 </li>
 </li>
 <li class="toctree-l1"><a class="reference internal" href="devman.html">FlatCAM Developer Manual</a><ul>
 <li class="toctree-l1"><a class="reference internal" href="devman.html">FlatCAM Developer Manual</a><ul>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#options">Options</a></li>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#options">Options</a></li>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#serialization">Serialization</a></li>
 <li class="toctree-l2"><a class="reference internal" href="devman.html#serialization">Serialization</a></li>
+<li class="toctree-l2"><a class="reference internal" href="devman.html#geometry-processing">Geometry Processing</a></li>
 </ul>
 </ul>
 </li>
 </li>
 </ul>
 </ul>

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
doc/build/searchindex.js


+ 12 - 0
doc/source/app.rst

@@ -14,3 +14,15 @@ PlotCanvas
 
 
 .. autoclass:: PlotCanvas
 .. autoclass:: PlotCanvas
     :members:
     :members:
+
+ObjectCollection
+~~~~~~~~~~~~~~~~
+
+.. autoclass:: ObjectCollection
+    :members:
+
+Measurement
+~~~~~~~~~~~
+
+.. autoclass:: Measurement
+    :members:

+ 1 - 1
recent.json

@@ -1 +1 @@
-[{"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"}]
+[{"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\TFTadapter.drl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\PlacaReles-F_Cu.gtl"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\PlacaReles.drl"}, {"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": "project", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\RTWO_fc5_3.fcproj"}, {"kind": "project", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\RTWO_fc5_2.fcproj"}, {"kind": "project", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\RTWO_fc5.fcproj"}, {"kind": "cncjob", "filename": "Z:\\CNC\\testpcb\\2\\noname-F_Cu_ISOLATION_GCODE3.ngc"}, {"kind": "cncjob", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\RTWO1_CNC\\iso_bottom1.gcode"}]

Некоторые файлы не были показаны из-за большого количества измененных файлов