瀏覽代碼

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

Juan Pablo Caram 11 年之前
父節點
當前提交
6c13b7dc59
共有 16 個文件被更改,包括 518 次插入314 次删除
  1. 228 272
      FlatCAM.py
  2. 92 0
      FlatCAM.ui
  3. 3 0
      FlatCAMObj.py
  4. 7 2
      camlib.py
  5. 1 1
      defaults.json
  6. 二進制
      doc/build/.doctrees/camlib.doctree
  7. 二進制
      doc/build/.doctrees/environment.pickle
  8. 74 13
      doc/build/camlib.html
  9. 88 25
      doc/build/genindex.html
  10. 6 0
      doc/build/index.html
  11. 二進制
      doc/build/objects.inv
  12. 3 0
      doc/build/py-modindex.html
  13. 3 0
      doc/build/search.html
  14. 0 0
      doc/build/searchindex.js
  15. 12 0
      doc/source/app.rst
  16. 1 1
      recent.json

+ 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}

二進制
doc/build/.doctrees/camlib.doctree


二進制
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>

二進制
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>

File diff suppressed because it is too large
+ 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"}]

Some files were not shown because too many files changed in this diff