Bläddra i källkod

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

Juan Pablo Caram 11 år sedan
förälder
incheckning
6c13b7dc59

+ 228 - 272
FlatCAM.py

@@ -36,6 +36,7 @@ from camlib import *
 from FlatCAMObj import *
 from FlatCAMWorker import Worker
 
+
 ########################################
 ##                App                 ##
 ########################################
@@ -149,13 +150,9 @@ class App:
                 #     self.radios_inv.update({obj.kind + "_" + option: obj.radios_inv[option]})
 
         ## Event subscriptions ##
-        # TODO: Move to plotcanvas
-        self.plot_click_subscribers = {}
-        self.plot_mousemove_subscribers = {}
 
         ## 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
         # 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')
@@ -188,10 +185,13 @@ class App:
         def somethreadfunc(app_obj):
             print "Hello World!"
 
+        self.message_dialog("Starting", "The best program is starting")
+
         t = threading.Thread(target=somethreadfunc, args=(self,))
         t.daemon = True
         t.start()
 
+
         ########################################
         ##              START                 ##
         ########################################
@@ -203,6 +203,30 @@ class App:
         self.window.set_default_size(900, 600)
         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):
 
         # Zoom fit
@@ -255,48 +279,6 @@ class App:
         """
         FlatCAMObj.app = self
 
-    # def setup_project_list(self):
-    #     """
-    #     Sets up list or Tree where whatever has been loaded or created is
-    #     displayed.
-    #
-    #     :return: None
-    #     """
-    #
-    #     # 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):
         """
         Initial configuration of the component editor. Creates
@@ -335,7 +317,7 @@ class App:
             'gerber': self.open_gerber,
             'excellon': self.open_excellon,
             'cncjob': self.open_gcode,
-            'project': lambda x: ""
+            'project': self.open_project
         }
 
         # Closure needed to create callbacks in a loop.
@@ -378,39 +360,13 @@ class App:
 
     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.
         :type text: str
         :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):
         """
@@ -474,31 +430,11 @@ class App:
             self.info("Could not evaluate: " + value)
             return None
 
-    # def set_list_selection(self, name):
-    #     """
-    #     Marks a given object as selected in the list ob objects
-    #     in the GUI. This selection will in turn trigger
-    #     ``self.on_tree_selection_changed()``.
-    #
-    #     :param name: Name of the object.
-    #     :type name: str
-    #     :return: None
-    #     """
-    #
-    #     iter = self.store.get_iter_first()
-    #     while iter is not None and self.store[iter][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):
         """
         Creates a new specalized FlatCAMObj and attaches it to the application,
         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',
          'excellon', 'cncjob' and 'geometry'.
@@ -513,6 +449,8 @@ class App:
         :rtype: None
         """
 
+        print "new_object()"
+
         ### Check for existing name
         if name in self.collection.get_names():
             ## Create a new name
@@ -553,18 +491,17 @@ class App:
             obj.convert_units(self.options["units"])
 
         # 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.
-        GLib.timeout_add(100, lambda: self.notebook.set_current_page(1))
+        GLib.idle_add(lambda: self.notebook.set_current_page(1))
 
         # Plot
         # TODO: (Thread-safe?)
         obj.plot()
 
-        # TODO: Threading dissaster!
         GLib.idle_add(lambda: self.on_zoom_fit(None))
+        #self.on_zoom_fit(None)
 
         return obj
 
@@ -582,23 +519,6 @@ class App:
         self.progress_bar.set_fraction(percentage)
         return False
 
-    # def get_current(self):
-    #     """
-    #     Returns the currently selected FlatCAMObj in the application.
-    #
-    #     :return: Currently selected FlatCAMObj in the application.
-    #     :rtype: FlatCAMObj or None
-    #     """
-    #
-    #     # TODO: Could possibly read the form into the object here.
-    #     # But there are some cases when the form for the object
-    #     # is not up yet. See on_tree_selection_changed.
-    #
-    #     try:
-    #         return self.stuff[self.selected_item_name]
-    #     except:
-    #         return None
-
     def load_defaults(self):
         """
         Loads the aplication's default settings from defaults.json into
@@ -738,6 +658,7 @@ class App:
         except:
             pass
 
+        # Serialize the whole project
         d = {"objs": [obj.to_dict() for obj in self.collection.get_list()],
              "options": self.options}
 
@@ -786,7 +707,7 @@ class App:
         # Project options
         self.options.update(d['options'])
         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
         for obj in d['objs']:
@@ -961,6 +882,9 @@ class App:
     ########################################
     ##         EVENT HANDLERS             ##
     ########################################
+    def on_debug_printlist(self, *args):
+        self.collection.print_list()
+
     def on_disable_all_plots(self, widget):
         self.disable_plots()
 
@@ -997,8 +921,6 @@ class App:
         :return: None
         """
 
-        # self.get_current().read_form()
-        # self.get_current().plot()
         self.collection.get_active().read_form()
         self.collection.get_active().plot()
 
@@ -1012,7 +934,6 @@ class App:
 
         about = self.builder.get_object("aboutdialog")
         response = about.run()
-        #about.destroy()
         about.hide()
 
     def on_create_mirror(self, widget):
@@ -1299,6 +1220,18 @@ class App:
 
         def on_success(app_obj, filename):
             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)
             self.project_filename = filename
             self.register_recent("project", filename)
@@ -1319,6 +1252,18 @@ class App:
 
         def on_success(app_obj, filename):
             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)
             self.register_recent("project", filename)
             app_obj.info("Project copy saved to: " + filename)
@@ -1578,9 +1523,7 @@ class App:
             obj.plot()
             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])
 
     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.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])
 
     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.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])
 
     def on_generate_paintarea(self, widget):
@@ -1843,10 +1780,14 @@ class App:
         tooldia = geo.options["painttooldia"]
         overlap = geo.options["paintoverlap"]
 
+        # Connection ID for the click event
+        subscription = None
+
         # To be called after clicking on the plot.
         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]
             poly = find_polygon(geo.solid_geometry, point)
 
@@ -1858,10 +1799,12 @@ class App:
                 geo_obj.solid_geometry = cp
                 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.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):
         """
@@ -1940,23 +1883,25 @@ class App:
     def on_file_new(self, param):
         """
         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.
         :return: None
         """
         # 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
         self.project_filename = None
@@ -2006,13 +1951,10 @@ class App:
         if response == Gtk.ResponseType.OK:
             filename = dialog.get_filename()
             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])
-            #on_success(self, filename)
         elif response == Gtk.ResponseType.CANCEL:
-            self.info("Open cancelled.")  # print("Cancel clicked")
+            self.info("Open cancelled.")
             dialog.destroy()
 
     def file_chooser_save_action(self, on_success):
@@ -2043,8 +1985,12 @@ class App:
 
         def obj_init(gerber_obj, app_obj):
             assert isinstance(gerber_obj, FlatCAMGerber)
+
+            # Opening the file happens here
             GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Parsing ..."))
             gerber_obj.parse_file(filename)
+
+            # Further parsing
             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 ..."))
@@ -2053,8 +1999,9 @@ class App:
         self.new_object("gerber", name, obj_init)
         self.register_recent("gerber", filename)
 
+        self.info("Opened: " + filename)
         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):
         """
@@ -2065,30 +2012,7 @@ class App:
         :param param: Ignore
         :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))
 
     def open_excellon(self, filename):
@@ -2104,6 +2028,7 @@ class App:
         self.new_object("excellon", name, obj_init)
         self.register_recent("excellon", filename)
 
+        self.info("Opened: " + filename)
         GLib.idle_add(lambda: self.set_progress_bar(1.0, "Done!"))
         GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, ""))
 
@@ -2116,28 +2041,7 @@ class App:
         :param param: Ignore
         :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))
 
     def open_gcode(self, filename):
@@ -2168,6 +2072,7 @@ class App:
         self.new_object("cncjob", name, obj_init)
         self.register_recent("cncjob", filename)
 
+        self.info("Opened: " + filename)
         GLib.idle_add(lambda: self.set_progress_bar(1.0, "Done!"))
         GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, ""))
 
@@ -2180,43 +2085,7 @@ class App:
         :param param: Ignore
         :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))
 
     def on_mouse_move_over_plot(self, event):
@@ -2234,8 +2103,8 @@ class App:
                 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:
             self.position_label.set_label("")
@@ -2256,7 +2125,7 @@ class App:
         :return: None
         """
 
-        # For key presses
+        # So it can receive key presses
         self.plotcanvas.canvas.grab_focus()
 
         try:
@@ -2264,8 +2133,8 @@ class App:
                 event.button, event.x, event.y, event.xdata, event.ydata)
 
             # 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)
 
@@ -2403,31 +2272,32 @@ class DrawingPoint(DrawingObject):
 
 
 class Measurement:
-    def __init__(self, container, axes, click_subscibers, move_subscribers, update=None):
+    def __init__(self, container, plotcanvas, update=None):
         self.update = update
         self.container = container
         self.frame = None
         self.label = None
-        self.click_subscribers = click_subscibers
-        self.move_subscribers = move_subscribers
         self.point1 = None
         self.point2 = None
         self.active = False
+        self.plotcanvas = plotcanvas
+        self.click_subscription = None
+        self.move_subscription = None
 
     def toggle_active(self, *args):
         if self.active:  # Deactivate
             self.active = False
-            self.move_subscribers.pop("meas")
-            self.click_subscribers.pop("meas")
             self.container.remove(self.frame)
             if self.update is not None:
                 self.update()
+            self.plotcanvas.mpl_disconnect(self.click_subscription)
+            self.plotcanvas.mpl_disconnect(self.move_subscription)
             return False
         else:  # Activate
             print "DEBUG: Activating Measurement Tool..."
             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.set_margin_right(5)
             self.frame.set_margin_top(3)
@@ -2449,10 +2319,13 @@ class Measurement:
         if self.point1 is None:
             self.label.set_label("Click on a reference point...")
         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:
             self.update()
 
@@ -2540,9 +2413,18 @@ class PlotCanvas:
         :type event_name: str
         :param callback: Function to call
         :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):
         """
@@ -2592,6 +2474,8 @@ class PlotCanvas:
         :return: None
         """
 
+        print "PC.adjust_axes()"
+
         width = xmax - xmin
         height = ymax - ymin
         try:
@@ -2707,21 +2591,6 @@ class PlotCanvas:
 
         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):
         """
         Scroll event handler.
@@ -2730,6 +2599,11 @@ class PlotCanvas:
         :param event: Event object containing the event information.
         :return: None
         """
+
+        # So it can receive key presses
+        self.canvas.grab_focus()
+
+        # Event info
         z, direction = event.get_scroll_direction()
 
         if self.key is None:
@@ -2758,7 +2632,7 @@ class PlotCanvas:
 
     def on_mouse_move(self, event):
         """
-        Mouse movement event hadler.
+        Mouse movement event hadler. Stores the coordinates.
 
         :param event: Contains information about the event.
         :return: None
@@ -2822,6 +2696,13 @@ class ObjectCollection:
         column_text.set_cell_data_func(renderer_text, _set_cell_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):
         print "OC.delete_all()"
         # self.collection = []
@@ -2837,10 +2718,22 @@ class ObjectCollection:
             pass
 
     def on_row_activated(self, *args):
+        """
+        Does nothing right now.
+        :param args: Ignored.
+        :return: None
+        """
         print "OC.on_row_activated()"
         return
 
     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()"
         try:
             self.get_active().build_ui()
@@ -2851,6 +2744,14 @@ class ObjectCollection:
         # TODO: active, so cannot read form.
 
     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()"
         self.set_list_selection(name)
 
@@ -2863,6 +2764,13 @@ class ObjectCollection:
             return None
 
     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()"
         iterat = self.store.get_iter_first()
         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)
 
     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()"
 
-        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):
+        """
+        Gets a list of the names of all objects in the collection.
+
+        :return: List of names.
+        :rtype: list
+        """
         print "OC.get_names()"
         names = []
         iterat = self.store.get_iter_first()
@@ -2887,6 +2812,12 @@ class ObjectCollection:
         return names
 
     def get_bounds(self):
+        """
+        Finds coordinates bounding all objects in the collection.
+
+        :return: [xmin, ymin, xmax, ymax]
+        :rtype: list
+        """
         print "OC.get_bounds()"
 
         # TODO: Move the operation out of here.
@@ -2911,6 +2842,12 @@ class ObjectCollection:
         return [xmin, ymin, xmax, ymax]
 
     def get_list(self):
+        """
+        Returns a list with all FlatCAMObj.
+
+        :return: List with all FlatCAMObj.
+        :rtype: list
+        """
         collection_list = []
         iterat = self.store.get_iter_first()
         while iterat is not None:
@@ -2920,6 +2857,14 @@ class ObjectCollection:
         return collection_list
 
     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()
         while iterat is not None:
             obj = self.store[iterat][0]
@@ -2929,6 +2874,17 @@ class ObjectCollection:
         return None
 
     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()
         while iterat is not None:
             obj = self.store[iterat][0]

+ 92 - 0
FlatCAM.ui

@@ -97,6 +97,11 @@ THE SOFTWARE.</property>
     <property name="can_focus">False</property>
     <property name="stock">gtk-open</property>
   </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">
     <property name="visible">True</property>
     <property name="can_focus">False</property>
@@ -132,6 +137,77 @@ THE SOFTWARE.</property>
     <property name="can_focus">False</property>
     <property name="stock">gtk-open</property>
   </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">
     <property name="can_focus">False</property>
     <child>
@@ -3488,6 +3564,22 @@ to application defaults.</property>
                         <signal name="activate" handler="on_tools_doublesided" swapped="no"/>
                       </object>
                     </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>
                 </child>
               </object>

+ 3 - 0
FlatCAMObj.py

@@ -38,6 +38,9 @@ class FlatCAMObj(GObject.GObject, object):
         self.axes = None  # Matplotlib axes
         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):
         """
         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
         # 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'(?=.*B(\d*\.?\d*))?(?=.*H(\d*\.?\d*))?' +
                                      r'(?=.*Z([-\+]?\d*\.?\d*))?[CFSBHT]')
@@ -1494,7 +1498,8 @@ class Excellon(Geometry):
         # Can have additional data after tool number but
         # is ignored if present in the header.
         # 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
         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-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#objectcollection">ObjectCollection</a></li>
+<li class="toctree-l2"><a class="reference internal" href="app.html#measurement">Measurement</a></li>
 </ul>
 </li>
 <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#serialization">Serialization</a></li>
+<li class="toctree-l2"><a class="reference internal" href="devman.html#geometry-processing">Geometry Processing</a></li>
 </ul>
 </li>
 </ul>
@@ -523,7 +527,19 @@ box in both positive and negative, x and y axes.</li>
 <dl class="method">
 <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>
-<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-body" />
 <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.
 These are:</p>
 <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">flashes</span></tt></li>
 </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">
 <col class="field-name" />
 <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.
 These are:</p>
 <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">flashes</span></tt></li>
 </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>
@@ -668,6 +695,23 @@ list of length n.</p>
 </table>
 </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">
 <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>
@@ -833,6 +877,23 @@ are stored in <tt class="docutils literal"><span class="pre">self.primitives</sp
 </table>
 </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>
 
 </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-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#objectcollection">ObjectCollection</a></li>
+<li class="toctree-l2"><a class="reference internal" href="app.html#measurement">Measurement</a></li>
 </ul>
 </li>
 <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#serialization">Serialization</a></li>
+<li class="toctree-l2"><a class="reference internal" href="devman.html#geometry-processing">Geometry Processing</a></li>
 </ul>
 </li>
 </ul>
@@ -187,6 +190,12 @@
   <dt><a href="camlib.html#camlib.ApertureMacro.append">append() (camlib.ApertureMacro method)</a>
   </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>
@@ -208,10 +217,6 @@
   </dl></td>
   <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>
 
@@ -226,6 +231,10 @@
   </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>
 
@@ -233,12 +242,12 @@
   <dt><a href="camlib.html#camlib.Geometry.clear_polygon">clear_polygon() (camlib.Geometry method)</a>
   </dt>
 
+  </dl></td>
+  <td style="width: 33%" valign="top"><dl>
       
   <dt><a href="camlib.html#camlib.CNCjob">CNCjob (class in camlib)</a>
   </dt>
 
-  </dl></td>
-  <td style="width: 33%" valign="top"><dl>
       
   <dt><a href="app.html#FlatCAM.PlotCanvas.connect">connect() (FlatCAM.PlotCanvas method)</a>
   </dt>
@@ -280,6 +289,10 @@
   </dl></td>
   <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>
 
@@ -342,9 +355,15 @@
   </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>
 
+      </dl></dd>
   </dl></td>
 </tr></table>
 
@@ -375,14 +394,18 @@
   <dt><a href="camlib.html#camlib.Gerber">Gerber (class in camlib)</a>
   </dt>
 
+      
+  <dt><a href="camlib.html#camlib.Gerber.get_bounding_box">get_bounding_box() (camlib.Gerber method)</a>
+  </dt>
+
   </dl></td>
   <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><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>
 
       
@@ -394,6 +417,14 @@
   </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>
 
@@ -463,12 +494,12 @@
   <dt><a href="camlib.html#camlib.ApertureMacro.make_outline">make_outline() (camlib.ApertureMacro static method)</a>
   </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>
 
+  </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>
@@ -478,6 +509,10 @@
   </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>
 
@@ -491,6 +526,10 @@
   <dt><a href="app.html#FlatCAM.PlotCanvas.mpl_connect">mpl_connect() (FlatCAM.PlotCanvas method)</a>
   </dt>
 
+      
+  <dt><a href="app.html#FlatCAM.PlotCanvas.mpl_disconnect">mpl_disconnect() (FlatCAM.PlotCanvas method)</a>
+  </dt>
+
   </dl></td>
 </tr></table>
 
@@ -514,6 +553,10 @@
 <table style="width: 100%" class="indextable genindextable"><tr>
   <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>
 
@@ -647,12 +690,12 @@
   <dt><a href="app.html#FlatCAM.App.on_generate_isolation">on_generate_isolation() (FlatCAM.App method)</a>
   </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>
 
+  </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>
@@ -662,10 +705,22 @@
   </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>
 
       
+  <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>
 
@@ -713,6 +768,12 @@
   <dt><a href="app.html#FlatCAM.App.on_row_activated">on_row_activated() (FlatCAM.App method)</a>
   </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>
@@ -738,10 +799,6 @@
   </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>
 
@@ -896,6 +953,10 @@
   </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>
 
@@ -906,15 +967,15 @@
 
       </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>
 
+  </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>
 
-  </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>
@@ -928,10 +989,6 @@
   </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>
 
@@ -942,9 +999,15 @@
 <table style="width: 100%" class="indextable genindextable"><tr>
   <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>
 
+      <dd><dl>
+        
+  <dt><a href="camlib.html#camlib.Geometry.to_dict">(camlib.Geometry method)</a>
+  </dt>
+
+      </dl></dd>
   </dl></td>
   <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-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#objectcollection">ObjectCollection</a></li>
+<li class="toctree-l2"><a class="reference internal" href="app.html#measurement">Measurement</a></li>
 </ul>
 </li>
 <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#serialization">Serialization</a></li>
+<li class="toctree-l2"><a class="reference internal" href="devman.html#geometry-processing">Geometry Processing</a></li>
 </ul>
 </li>
 </ul>
@@ -164,11 +167,14 @@
 <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#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>
 </li>
 <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#serialization">Serialization</a></li>
+<li class="toctree-l2"><a class="reference internal" href="devman.html#geometry-processing">Geometry Processing</a></li>
 </ul>
 </li>
 </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-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#objectcollection">ObjectCollection</a></li>
+<li class="toctree-l2"><a class="reference internal" href="app.html#measurement">Measurement</a></li>
 </ul>
 </li>
 <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#serialization">Serialization</a></li>
+<li class="toctree-l2"><a class="reference internal" href="devman.html#geometry-processing">Geometry Processing</a></li>
 </ul>
 </li>
 </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-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#objectcollection">ObjectCollection</a></li>
+<li class="toctree-l2"><a class="reference internal" href="app.html#measurement">Measurement</a></li>
 </ul>
 </li>
 <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#serialization">Serialization</a></li>
+<li class="toctree-l2"><a class="reference internal" href="devman.html#geometry-processing">Geometry Processing</a></li>
 </ul>
 </li>
 </ul>

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
doc/build/searchindex.js


+ 12 - 0
doc/source/app.rst

@@ -14,3 +14,15 @@ PlotCanvas
 
 .. autoclass:: PlotCanvas
     :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"}]

Vissa filer visades inte eftersom för många filer har ändrats