Преглед изворни кода

Use of logging instead of print statements.

Juan Pablo Caram пре 11 година
родитељ
комит
582e472e12
10 измењених фајлова са 730 додато и 611 уклоњено
  1. 1 1
      FlatCAM.ui
  2. 215 515
      FlatCAMApp.py
  3. 66 29
      FlatCAMObj.py
  4. 45 9
      GUIElements.py
  5. 14 10
      ObjectCollection.py
  6. 39 16
      ObjectUI.py
  7. 310 0
      PlotCanvas.py
  8. 38 29
      camlib.py
  9. 1 1
      defaults.json
  10. 1 1
      recent.json

+ 1 - 1
FlatCAM.ui

@@ -6,7 +6,7 @@
     <property name="border_width">5</property>
     <property name="type_hint">dialog</property>
     <property name="program_name">FlatCAM</property>
-    <property name="version">Version Alpha 3 (2014/03) - UNSTABLE</property>
+    <property name="version">Version Alpha 5 (2014/05)</property>
     <property name="copyright" translatable="yes">(c) 2014 Juan Pablo Caram</property>
     <property name="comments" translatable="yes">2D Post-processing for Manufacturing specialized in 
 Printed Circuit Boards</property>

Разлика између датотеке није приказан због своје велике величине
+ 215 - 515
FlatCAMApp.py


+ 66 - 29
FlatCAMObj.py

@@ -13,7 +13,7 @@ from gi.repository import GObject
 
 import inspect  # TODO: Remove
 
-from FlatCAMApp import *
+import FlatCAMApp
 from camlib import *
 from ObjectUI import *
 
@@ -123,15 +123,15 @@ class FlatCAMObj(GObject.GObject, object):
         """
 
         if self.axes is None:
-            print "New axes"
+            FlatCAMApp.App.log.debug("setup_axes(): New axes")
             self.axes = figure.add_axes([0.05, 0.05, 0.9, 0.9],
                                         label=self.options["name"])
         elif self.axes not in figure.axes:
-            print "Clearing and attaching axes"
+            FlatCAMApp.App.log.debug("setup_axes(): Clearing and attaching axes")
             self.axes.cla()
             figure.add_axes(self.axes)
         else:
-            print "Clearing Axes"
+            FlatCAMApp.App.log.debug("setup_axes(): Clearing Axes")
             self.axes.cla()
 
         # Remove all decoration. The app's axes will have
@@ -158,7 +158,7 @@ class FlatCAMObj(GObject.GObject, object):
         :return: None
         :rtype: None
         """
-        print inspect.stack()[1][3], "--> FlatCAMObj.read_form()"
+        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> FlatCAMObj.read_form()")
         for option in self.options:
             self.read_form_item(option)
 
@@ -171,7 +171,7 @@ class FlatCAMObj(GObject.GObject, object):
         """
 
         self.muted_ui = True
-        print inspect.stack()[1][3], "--> FlatCAMObj.build_ui()"
+        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> FlatCAMObj.build_ui()")
 
         # Where the UI for this object is drawn
         # box_selected = self.app.builder.get_object("box_selected")
@@ -186,8 +186,8 @@ class FlatCAMObj(GObject.GObject, object):
         # box_selected.pack_start(sw, True, True, 0)
         box_selected.add(self.ui)
         self.to_form()
-        box_selected.show_all()
-        self.ui.show()
+        GLib.idle_add(box_selected.show_all)
+        GLib.idle_add(self.ui.show_all)
         self.muted_ui = False
 
     def set_form_item(self, option):
@@ -202,7 +202,7 @@ class FlatCAMObj(GObject.GObject, object):
         try:
             self.form_fields[option].set_value(self.options[option])
         except KeyError:
-            App.log.warn("Tried to set an option or field that does not exist: %s" % option)
+            self.app.log.warn("Tried to set an option or field that does not exist: %s" % option)
 
     def read_form_item(self, option):
         """
@@ -216,7 +216,7 @@ class FlatCAMObj(GObject.GObject, object):
         try:
             self.options[option] = self.form_fields[option].get_value()
         except KeyError:
-            App.log.warning("Failed to read option from field: %s" % option)
+            self.app.log.warning("Failed to read option from field: %s" % option)
 
     def plot(self):
         """
@@ -497,8 +497,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
                                          zorder=2)
                     self.axes.add_patch(patch)
                 except AssertionError:
-                    print "WARNING: A geometry component was not a polygon:"
-                    print poly
+                    FlatCAMApp.App.log.warning("A geometry component was not a polygon:")
+                    FlatCAMApp.App.log.warning(str(poly))
         else:
             for poly in geometry:
                 x, y = poly.exterior.xy
@@ -547,15 +547,6 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             "toolselection": ""
         })
 
-        # self.form_kinds.update({
-        #     "plot": "cb",
-        #     "solid": "cb",
-        #     "drillz": "entry_eval",
-        #     "travelz": "entry_eval",
-        #     "feedrate": "entry_eval",
-        #     "toolselection": "entry_text"
-        # })
-
         # TODO: Document this.
         self.tool_cbs = {}
 
@@ -564,10 +555,49 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
         # from predecessors.
         self.ser_attrs += ['options', 'kind']
 
+        assert isinstance(self.ui, ExcellonObjectUI)
         self.ui.plot_cb.connect('clicked', self.on_plot_cb_click)
         self.ui.plot_cb.connect('activate', self.on_plot_cb_click)
         self.ui.solid_cb.connect('clicked', self.on_solid_cb_click)
         self.ui.solid_cb.connect('activate', self.on_solid_cb_click)
+        self.ui.choose_tools_button.connect('clicked', lambda args: self.show_tool_chooser())
+        self.ui.choose_tools_button.connect('activate', lambda args: self.show_tool_chooser())
+        self.ui.generate_cnc_button.connect('clicked', self.on_create_cncjob_button_click)
+        self.ui.generate_cnc_button.connect('activate', self.on_create_cncjob_button_click)
+
+    def on_create_cncjob_button_click(self, *args):
+        self.read_form()
+        job_name = self.options["name"] + "_cnc"
+
+        # Object initialization function for app.new_object()
+        def job_init(job_obj, app_obj):
+            assert isinstance(job_obj, FlatCAMCNCjob)
+
+            GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Creating CNC Job..."))
+            job_obj.z_cut = self.options["drillz"]
+            job_obj.z_move = self.options["travelz"]
+            job_obj.feedrate = self.options["feedrate"]
+            # There could be more than one drill size...
+            # job_obj.tooldia =   # TODO: duplicate variable!
+            # job_obj.options["tooldia"] =
+            job_obj.generate_from_excellon_by_tool(self, self.options["toolselection"])
+
+            GLib.idle_add(lambda: app_obj.set_progress_bar(0.5, "Parsing G-Code..."))
+            job_obj.gcode_parse()
+
+            GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Creating New Geometry..."))
+            job_obj.create_geometry()
+
+            GLib.idle_add(lambda: app_obj.set_progress_bar(0.8, "Plotting..."))
+
+        # To be run in separate thread
+        def job_thread(app_obj):
+            app_obj.new_object("cncjob", job_name, job_init)
+            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, ""))
+
+        # Send to worker
+        self.app.worker.add_task(job_thread, [self.app])
 
     def on_plot_cb_click(self, *args):
         if self.muted_ui:
@@ -669,11 +699,6 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
             "tooldia": self.ui.tooldia_entry
         })
 
-        # self.form_kinds.update({
-        #     "plot": "cb",
-        #     "tooldia": "entry_eval"
-        # })
-
         # Attributes to be included in serialization
         # Always append to it because it carries contents
         # from predecessors.
@@ -682,6 +707,18 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
         self.ui.plot_cb.connect('clicked', self.on_plot_cb_click)
         self.ui.plot_cb.connect('activate', self.on_plot_cb_click)
 
+        self.ui.export_gcode_button.connect('clicked', self.on_exportgcode_button_click)
+        self.ui.export_gcode_button.connect('activate', self.on_exportgcode_button_click)
+
+    def on_exportgcode_button_click(self, *args):
+        def on_success(app_obj, filename):
+            f = open(filename, 'w')
+            f.write(self.gcode)
+            f.close()
+            app_obj.info("Saved to: " + filename)
+
+        self.app.file_chooser_save_action(on_success)
+
     def on_plot_cb_click(self, *args):
         if self.muted_ui:
             return
@@ -702,7 +739,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
 
     def convert_units(self, units):
         factor = CNCjob.convert_units(self, units)
-        print "FlatCAMCNCjob.convert_units()"
+        FlatCAMApp.App.log.debug("FlatCAMCNCjob.convert_units()")
         self.options["tooldia"] *= factor
 
 
@@ -795,7 +832,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                 geo_obj.options["cnctooldia"] = tooldia
 
             name = self.options["name"] + "_paint"
-            self.new_object("geometry", name, gen_paintarea)
+            self.app.new_object("geometry", name, gen_paintarea)
 
         subscription = self.app.plotcanvas.mpl_connect('button_press_event', doit)
 
@@ -934,7 +971,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                         self.axes.plot(x, y, 'r-')
                 continue
 
-            print "WARNING: Did not plot:", str(type(geo))
+            FlatCAMApp.App.log.warning("Did not plot:", str(type(geo)))
 
         #self.app.plotcanvas.auto_adjust_axes()
         GLib.idle_add(self.app.plotcanvas.auto_adjust_axes)

+ 45 - 9
GUIElements.py

@@ -1,6 +1,15 @@
+############################################################
+# FlatCAM: 2D Post-processing for Manufacturing            #
+# http://caram.cl/software/flatcam                         #
+# Author: Juan Pablo Caram (c)                             #
+# Date: 2/5/2014                                           #
+# MIT Licence                                              #
+############################################################
+
 from gi.repository import Gtk
 import re
 from copy import copy
+import FlatCAMApp
 
 
 class RadioSet(Gtk.Box):
@@ -24,16 +33,20 @@ class RadioSet(Gtk.Box):
             else:
                 choice['radio'] = Gtk.RadioButton.new_with_label_from_widget(self.group, choice['label'])
             self.pack_start(choice['radio'], expand=True, fill=False, padding=2)
-            # choice['radio'].connect('toggled', self.on_toggle)
+            choice['radio'].connect('toggled', self.on_toggle)
+
+        self.group_toggle_fn = lambda x, y: None
 
-    # def on_toggle(self, *args):
-    #     return
+    def on_toggle(self, btn):
+        if btn.get_active():
+            self.group_toggle_fn(btn, self.get_value)
+        return
 
     def get_value(self):
         for choice in self.choices:
             if choice['radio'].get_active():
                 return choice['value']
-        print "ERROR: No button was toggled in RadioSet."
+        FlatCAMApp.App.log.error("No button was toggled in RadioSet.")
         return None
 
     def set_value(self, val):
@@ -41,7 +54,7 @@ class RadioSet(Gtk.Box):
             if choice['value'] == val:
                 choice['radio'].set_active(True)
                 return
-        print "ERROR: Value given is not part of this RadioSet:", val
+        FlatCAMApp.App.log.error("Value given is not part of this RadioSet: %s" % str(val))
 
 
 class LengthEntry(Gtk.Entry):
@@ -63,7 +76,7 @@ class LengthEntry(Gtk.Entry):
         if val is not None:
             self.set_text(str(val))
         else:
-            print "WARNING: Could not interpret entry:", self.get_text()
+            FlatCAMApp.App.log.warning("Could not interpret entry: %s" % self.get_text())
 
     def get_value(self):
         raw = self.get_text().strip(' ')
@@ -76,7 +89,7 @@ class LengthEntry(Gtk.Entry):
             else:
                 return float(match.group(1))
         except:
-            print "ERROR: Could not parse value in entry:", raw
+            FlatCAMApp.App.log.error("Could not parse value in entry: %s" % str(raw))
             return None
 
     def set_value(self, val):
@@ -94,14 +107,14 @@ class FloatEntry(Gtk.Entry):
         if val is not None:
             self.set_text(str(val))
         else:
-            print "WARNING: Could not interpret entry:", self.get_text()
+            FlatCAMApp.App.log.warning("Could not interpret entry: %s" % self.get_text())
 
     def get_value(self):
         raw = self.get_text().strip(' ')
         try:
             evaled = eval(raw)
         except:
-            print "ERROR: Could not evaluate:", raw
+            FlatCAMApp.App.log.error("Could not evaluate: %s" % str(raw))
             return None
 
         return float(evaled)
@@ -132,6 +145,29 @@ class FCEntry(Gtk.Entry):
         self.set_text(str(val))
 
 
+class EvalEntry(Gtk.Entry):
+    def __init__(self):
+        Gtk.Entry.__init__(self)
+
+    def on_activate(self, *args):
+        val = self.get_value()
+        if val is not None:
+            self.set_text(str(val))
+        else:
+            FlatCAMApp.App.log.warning("Could not interpret entry: %s" % self.get_text())
+
+    def get_value(self):
+        raw = self.get_text().strip(' ')
+        try:
+            return eval(raw)
+        except:
+            FlatCAMApp.App.log.error("Could not evaluate: %s" % str(raw))
+            return None
+
+    def set_value(self, val):
+        self.set_text(str(val))
+
+
 class FCCheckBox(Gtk.CheckButton):
     def __init__(self, label=''):
         Gtk.CheckButton.__init__(self, label=label)

+ 14 - 10
ObjectCollection.py

@@ -9,6 +9,7 @@
 from FlatCAMObj import *
 from gi.repository import Gtk, GdkPixbuf
 import inspect  # TODO: Remove
+import FlatCAMApp
 
 
 class ObjectCollection:
@@ -75,11 +76,11 @@ class ObjectCollection:
             iterat = self.store.iter_next(iterat)
 
     def delete_all(self):
-        print "OC.delete_all()"
+        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.delete_all()")
         self.store.clear()
 
     def delete_active(self):
-        print "OC.delete_active()"
+        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.delete_active()")
         try:
             model, treeiter = self.tree_selection.get_selected()
             self.store.remove(treeiter)
@@ -94,7 +95,7 @@ class ObjectCollection:
         :param selection: Ignored.
         :return: None
         """
-        print inspect.stack()[1][3], "--> OC.on_list_selection_change()"
+        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.on_list_selection_change()")
         try:
             self.get_active().build_ui()
         except AttributeError:  # For None being active
@@ -109,11 +110,11 @@ class ObjectCollection:
         :type name: str
         :return: None
         """
-        print "OC.set_active()"
+        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.set_active()")
         self.set_list_selection(name)
 
     def get_active(self):
-        print inspect.stack()[1][3], "--> OC.get_active()"
+        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_active()")
         try:
             model, treeiter = self.tree_selection.get_selected()
             return model[treeiter][0]
@@ -128,7 +129,7 @@ class ObjectCollection:
         :rtype name: str
         :return: None
         """
-        print inspect.stack()[1][3], "--> OC.set_list_selection()"
+        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.set_list_selection()")
         iterat = self.store.get_iter_first()
         while iterat is not None and self.store[iterat][0].options["name"] != name:
             iterat = self.store.iter_next(iterat)
@@ -144,7 +145,7 @@ class ObjectCollection:
         :type active: bool
         :return: None
         """
-        print inspect.stack()[1][3], "--> OC.append()"
+        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.append()")
 
         def guitask():
             self.store.append([obj])
@@ -159,7 +160,7 @@ class ObjectCollection:
         :return: List of names.
         :rtype: list
         """
-        print "OC.get_names()"
+        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_names()")
         names = []
         iterat = self.store.get_iter_first()
         while iterat is not None:
@@ -175,7 +176,7 @@ class ObjectCollection:
         :return: [xmin, ymin, xmax, ymax]
         :rtype: list
         """
-        print "OC.get_bounds()"
+        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_bounds()")
 
         # TODO: Move the operation out of here.
 
@@ -194,7 +195,7 @@ class ObjectCollection:
                 xmax = max([xmax, gxmax])
                 ymax = max([ymax, gymax])
             except:
-                print "DEV WARNING: Tried to get bounds of empty geometry."
+                FlatCAMApp.App.log.waring("DEV WARNING: Tried to get bounds of empty geometry.")
             iterat = self.store.iter_next(iterat)
         return [xmin, ymin, xmax, ymax]
 
@@ -205,6 +206,7 @@ class ObjectCollection:
         :return: List with all FlatCAMObj.
         :rtype: list
         """
+        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_list()")
         collection_list = []
         iterat = self.store.get_iter_first()
         while iterat is not None:
@@ -222,6 +224,8 @@ class ObjectCollection:
         :return: The requested object or None if no such object.
         :rtype: FlatCAMObj or None
         """
+        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_by_name()")
+
         iterat = self.store.get_iter_first()
         while iterat is not None:
             obj = self.store[iterat][0]

+ 39 - 16
ObjectUI.py

@@ -1,12 +1,20 @@
+############################################################
+# FlatCAM: 2D Post-processing for Manufacturing            #
+# http://caram.cl/software/flatcam                         #
+# Author: Juan Pablo Caram (c)                             #
+# Date: 2/5/2014                                           #
+# MIT Licence                                              #
+############################################################
 
 from gi.repository import Gtk
-import re
-from copy import copy
-
 from GUIElements import *
 
 
 class ObjectUI(Gtk.VBox):
+    """
+    Base class for the UI of FlatCAM objects.
+    """
+
     def __init__(self, icon_file='share/flatcam_icon32.png', title='FlatCAM Object'):
         Gtk.VBox.__init__(self, spacing=3, margin=5, vexpand=False)
 
@@ -26,7 +34,7 @@ class ObjectUI(Gtk.VBox):
 
         ## Object name
         self.name_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 2)
-        self.pack_start(self.name_box, expand=True, fill=False, padding=2)
+        self.pack_start(self.name_box, expand=False, fill=False, padding=2)
         name_label = Gtk.Label('Name:')
         name_label.set_justify(Gtk.Justification.RIGHT)
         self.name_box.pack_start(name_label,
@@ -42,10 +50,10 @@ class ObjectUI(Gtk.VBox):
         ## Scale
         self.scale_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
         self.scale_label.set_markup('<b>Scale:</b>')
-        self.pack_start(self.scale_label, expand=True, fill=False, padding=2)
+        self.pack_start(self.scale_label, expand=False, fill=False, padding=2)
 
         grid5 = Gtk.Grid(column_spacing=3, row_spacing=2)
-        self.pack_start(grid5, expand=True, fill=False, padding=2)
+        self.pack_start(grid5, expand=False, fill=False, padding=2)
 
         # Factor
         l10 = Gtk.Label('Factor:', xalign=1)
@@ -56,25 +64,25 @@ class ObjectUI(Gtk.VBox):
 
         # GO Button
         self.scale_button = Gtk.Button(label='Scale')
-        self.pack_start(self.scale_button, expand=True, fill=False, padding=2)
+        self.pack_start(self.scale_button, expand=False, fill=False, padding=2)
 
         ## Offset
         self.offset_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
         self.offset_label.set_markup('<b>Offset:</b>')
-        self.pack_start(self.offset_label, expand=True, fill=False, padding=2)
+        self.pack_start(self.offset_label, expand=False, fill=False, padding=2)
 
         grid6 = Gtk.Grid(column_spacing=3, row_spacing=2)
-        self.pack_start(grid6, expand=True, fill=False, padding=2)
+        self.pack_start(grid6, expand=False, fill=False, padding=2)
 
         # Vector
         l11 = Gtk.Label('Offset Vector:', xalign=1)
         grid6.attach(l11, 0, 0, 1, 1)
-        self.offsetvector_entry = FCEntry()
+        self.offsetvector_entry = EvalEntry()
         self.offsetvector_entry.set_text("(0.0, 0.0)")
         grid6.attach(self.offsetvector_entry, 1, 0, 1, 1)
 
         self.offset_button = Gtk.Button(label='Scale')
-        self.pack_start(self.offset_button, expand=True, fill=False, padding=2)
+        self.pack_start(self.offset_button, expand=False, fill=False, padding=2)
 
     def set_field(self, name, value):
         getattr(self, name).set_value(value)
@@ -84,16 +92,20 @@ class ObjectUI(Gtk.VBox):
 
 
 class CNCObjectUI(ObjectUI):
+    """
+    User interface for CNCJob objects.
+    """
+
     def __init__(self):
         ObjectUI.__init__(self, title='CNC Job Object', icon_file='share/cnc32.png')
 
         ## Plot options
         self.plot_options_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
         self.plot_options_label.set_markup("<b>Plot Options:</b>")
-        self.pack_start(self.plot_options_label, expand=False, fill=True, padding=2)
+        self.custom_box.pack_start(self.plot_options_label, expand=False, fill=True, padding=2)
 
         grid0 = Gtk.Grid(column_spacing=3, row_spacing=2)
-        self.pack_start(grid0, expand=True, fill=False, padding=2)
+        self.custom_box.pack_start(grid0, expand=False, fill=False, padding=2)
 
         # Plot CB
         self.plot_cb = FCCheckBox(label='Plot')
@@ -107,19 +119,23 @@ class CNCObjectUI(ObjectUI):
 
         # Update plot button
         self.updateplot_button = Gtk.Button(label='Update Plot')
-        self.pack_start(self.updateplot_button, expand=True, fill=False, padding=2)
+        self.custom_box.pack_start(self.updateplot_button, expand=False, fill=False, padding=2)
 
         ## Export G-Code
         self.export_gcode_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
         self.export_gcode_label.set_markup("<b>Export G-Code:</b>")
-        self.pack_start(self.export_gcode_label, expand=True, fill=False, padding=2)
+        self.custom_box.pack_start(self.export_gcode_label, expand=False, fill=False, padding=2)
 
         # GO Button
         self.export_gcode_button = Gtk.Button(label='Export G-Code')
-        self.pack_start(self.export_gcode_button, expand=True, fill=False, padding=2)
+        self.custom_box.pack_start(self.export_gcode_button, expand=False, fill=False, padding=2)
 
 
 class GeometryObjectUI(ObjectUI):
+    """
+    User interface for Geometry objects.
+    """
+
     def __init__(self):
         ObjectUI.__init__(self, title='Geometry Object', icon_file='share/geometry32.png')
 
@@ -200,6 +216,10 @@ class GeometryObjectUI(ObjectUI):
 
 
 class ExcellonObjectUI(ObjectUI):
+    """
+    User interface for Excellon objects.
+    """
+
     def __init__(self):
         ObjectUI.__init__(self, title='Excellon Object', icon_file='share/drill32.png')
 
@@ -254,6 +274,9 @@ class ExcellonObjectUI(ObjectUI):
 
 
 class GerberObjectUI(ObjectUI):
+    """
+    User interface for Gerber objects.
+    """
     def __init__(self):
         ObjectUI.__init__(self, title='Gerber Object')
 

+ 310 - 0
PlotCanvas.py

@@ -0,0 +1,310 @@
+from gi.repository import Gdk
+from matplotlib.figure import Figure
+from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas
+#from FlatCAMApp import *
+import FlatCAMApp
+
+
+class PlotCanvas:
+    """
+    Class handling the plotting area in the application.
+    """
+
+    def __init__(self, container):
+        """
+        The constructor configures the Matplotlib figure that
+        will contain all plots, creates the base axes and connects
+        events to the plotting area.
+
+        :param container: The parent container in which to draw plots.
+        :rtype: PlotCanvas
+        """
+        # Options
+        self.x_margin = 15  # pixels
+        self.y_margin = 25  # Pixels
+
+        # Parent container
+        self.container = container
+
+        # Plots go onto a single matplotlib.figure
+        self.figure = Figure(dpi=50)  # TODO: dpi needed?
+        self.figure.patch.set_visible(False)
+
+        # These axes show the ticks and grid. No plotting done here.
+        # New axes must have a label, otherwise mpl returns an existing one.
+        self.axes = self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label="base", alpha=0.0)
+        self.axes.set_aspect(1)
+        self.axes.grid(True)
+
+        # The canvas is the top level container (Gtk.DrawingArea)
+        self.canvas = FigureCanvas(self.figure)
+        self.canvas.set_hexpand(1)
+        self.canvas.set_vexpand(1)
+        self.canvas.set_can_focus(True)  # For key press
+
+        # Attach to parent
+        self.container.attach(self.canvas, 0, 0, 600, 400)  # TODO: Height and width are num. columns??
+
+        # Events
+        self.canvas.mpl_connect('motion_notify_event', self.on_mouse_move)
+        self.canvas.connect('configure-event', self.auto_adjust_axes)
+        self.canvas.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK)
+        self.canvas.connect("scroll-event", self.on_scroll)
+        self.canvas.mpl_connect('key_press_event', self.on_key_down)
+        self.canvas.mpl_connect('key_release_event', self.on_key_up)
+
+        self.mouse = [0, 0]
+        self.key = None
+
+    def on_key_down(self, event):
+        """
+
+        :param event:
+        :return:
+        """
+        self.key = event.key
+
+    def on_key_up(self, event):
+        """
+
+        :param event:
+        :return:
+        """
+        self.key = None
+
+    def mpl_connect(self, event_name, callback):
+        """
+        Attach an event handler to the canvas through the Matplotlib interface.
+
+        :param event_name: Name of the event
+        :type event_name: str
+        :param callback: Function to call
+        :type callback: func
+        :return: Connection id
+        :rtype: int
+        """
+        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):
+        """
+        Attach an event handler to the canvas through the native GTK interface.
+
+        :param event_name: Name of the event
+        :type event_name: str
+        :param callback: Function to call
+        :type callback: function
+        :return: Nothing
+        """
+        self.canvas.connect(event_name, callback)
+
+    def clear(self):
+        """
+        Clears axes and figure.
+
+        :return: None
+        """
+
+        # Clear
+        self.axes.cla()
+        try:
+            self.figure.clf()
+        except KeyError:
+            FlatCAMApp.App.log.warning("KeyError in MPL figure.clf()")
+
+        # Re-build
+        self.figure.add_axes(self.axes)
+        self.axes.set_aspect(1)
+        self.axes.grid(True)
+
+        # Re-draw
+        self.canvas.queue_draw()
+
+    def adjust_axes(self, xmin, ymin, xmax, ymax):
+        """
+        Adjusts all axes while maintaining the use of the whole canvas
+        and an aspect ratio to 1:1 between x and y axes. The parameters are an original
+        request that will be modified to fit these restrictions.
+
+        :param xmin: Requested minimum value for the X axis.
+        :type xmin: float
+        :param ymin: Requested minimum value for the Y axis.
+        :type ymin: float
+        :param xmax: Requested maximum value for the X axis.
+        :type xmax: float
+        :param ymax: Requested maximum value for the Y axis.
+        :type ymax: float
+        :return: None
+        """
+
+        FlatCAMApp.App.log.debug("PC.adjust_axes()")
+
+        width = xmax - xmin
+        height = ymax - ymin
+        try:
+            r = width / height
+        except ZeroDivisionError:
+            FlatCAMApp.App.log.error("Height is %f" % height)
+            return
+        canvas_w, canvas_h = self.canvas.get_width_height()
+        canvas_r = float(canvas_w) / canvas_h
+        x_ratio = float(self.x_margin) / canvas_w
+        y_ratio = float(self.y_margin) / canvas_h
+
+        if r > canvas_r:
+            ycenter = (ymin + ymax) / 2.0
+            newheight = height * r / canvas_r
+            ymin = ycenter - newheight / 2.0
+            ymax = ycenter + newheight / 2.0
+        else:
+            xcenter = (xmax + xmin) / 2.0
+            newwidth = width * canvas_r / r
+            xmin = xcenter - newwidth / 2.0
+            xmax = xcenter + newwidth / 2.0
+
+        # Adjust axes
+        for ax in self.figure.get_axes():
+            if ax._label != 'base':
+                ax.set_frame_on(False)  # No frame
+                ax.set_xticks([])  # No tick
+                ax.set_yticks([])  # No ticks
+                ax.patch.set_visible(False)  # No background
+                ax.set_aspect(1)
+            ax.set_xlim((xmin, xmax))
+            ax.set_ylim((ymin, ymax))
+            ax.set_position([x_ratio, y_ratio, 1 - 2 * x_ratio, 1 - 2 * y_ratio])
+
+        # Re-draw
+        self.canvas.queue_draw()
+
+    def auto_adjust_axes(self, *args):
+        """
+        Calls ``adjust_axes()`` using the extents of the base axes.
+
+        :rtype : None
+        :return: None
+        """
+
+        xmin, xmax = self.axes.get_xlim()
+        ymin, ymax = self.axes.get_ylim()
+        self.adjust_axes(xmin, ymin, xmax, ymax)
+
+    def zoom(self, factor, center=None):
+        """
+        Zooms the plot by factor around a given
+        center point. Takes care of re-drawing.
+
+        :param factor: Number by which to scale the plot.
+        :type factor: float
+        :param center: Coordinates [x, y] of the point around which to scale the plot.
+        :type center: list
+        :return: None
+        """
+
+        xmin, xmax = self.axes.get_xlim()
+        ymin, ymax = self.axes.get_ylim()
+        width = xmax - xmin
+        height = ymax - ymin
+
+        if center is None or center == [None, None]:
+            center = [(xmin + xmax) / 2.0, (ymin + ymax) / 2.0]
+
+        # For keeping the point at the pointer location
+        relx = (xmax - center[0]) / width
+        rely = (ymax - center[1]) / height
+
+        new_width = width / factor
+        new_height = height / factor
+
+        xmin = center[0] - new_width * (1 - relx)
+        xmax = center[0] + new_width * relx
+        ymin = center[1] - new_height * (1 - rely)
+        ymax = center[1] + new_height * rely
+
+        # Adjust axes
+        for ax in self.figure.get_axes():
+            ax.set_xlim((xmin, xmax))
+            ax.set_ylim((ymin, ymax))
+
+        # Re-draw
+        self.canvas.queue_draw()
+
+    def pan(self, x, y):
+        xmin, xmax = self.axes.get_xlim()
+        ymin, ymax = self.axes.get_ylim()
+        width = xmax - xmin
+        height = ymax - ymin
+
+        # Adjust axes
+        for ax in self.figure.get_axes():
+            ax.set_xlim((xmin + x*width, xmax + x*width))
+            ax.set_ylim((ymin + y*height, ymax + y*height))
+
+        # Re-draw
+        self.canvas.queue_draw()
+
+    def new_axes(self, name):
+        """
+        Creates and returns an Axes object attached to this object's Figure.
+
+        :param name: Unique label for the axes.
+        :return: Axes attached to the figure.
+        :rtype: Axes
+        """
+
+        return self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label=name)
+
+    def on_scroll(self, canvas, event):
+        """
+        Scroll event handler.
+
+        :param canvas: The widget generating the event. Ignored.
+        :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:
+
+            if direction is Gdk.ScrollDirection.UP:
+                self.zoom(1.5, self.mouse)
+            else:
+                self.zoom(1/1.5, self.mouse)
+            return
+
+        if self.key == 'shift':
+
+            if direction is Gdk.ScrollDirection.UP:
+                self.pan(0.3, 0)
+            else:
+                self.pan(-0.3, 0)
+            return
+
+        if self.key == 'ctrl+control':
+
+            if direction is Gdk.ScrollDirection.UP:
+                self.pan(0, 0.3)
+            else:
+                self.pan(0, -0.3)
+            return
+
+    def on_mouse_move(self, event):
+        """
+        Mouse movement event hadler. Stores the coordinates.
+
+        :param event: Contains information about the event.
+        :return: None
+        """
+        self.mouse = [event.xdata, event.ydata]

+ 38 - 29
camlib.py

@@ -27,6 +27,15 @@ import simplejson as json
 # TODO: Commented for FlatCAM packaging with cx_freeze
 #from matplotlib.pyplot import plot
 
+import logging
+
+log = logging.getLogger('base2')
+log.setLevel(logging.DEBUG)
+formatter = logging.Formatter('[%(levelname)s] %(message)s')
+handler = logging.StreamHandler()
+handler.setFormatter(formatter)
+log.addHandler(handler)
+
 
 class Geometry(object):
     def __init__(self):
@@ -57,7 +66,7 @@ class Geometry(object):
         of geometry: (xmin, ymin, xmax, ymax).
         """
         if self.solid_geometry is None:
-            print "Warning: solid_geometry not computed yet."
+            log.warning("solid_geometry not computed yet.")
             return (0, 0, 0, 0)
             
         if type(self.solid_geometry) == list:
@@ -72,7 +81,7 @@ class Geometry(object):
         bounds of geometry.
         """
         if self.solid_geometry is None:
-            print "Warning: solid_geometry not computed yet."
+            log.warning("Solid_geometry not computed yet.")
             return 0
         bounds = self.bounds()
         return (bounds[2]-bounds[0], bounds[3]-bounds[1])
@@ -133,7 +142,7 @@ class Geometry(object):
         :return: Scaling factor resulting from unit change.
         :rtype: float
         """
-        print "Geometry.convert_units()"
+        log.debug("Geometry.convert_units()")
 
         if units.upper() == self.units.upper():
             return 1.0
@@ -143,7 +152,7 @@ class Geometry(object):
         elif units.upper() == "IN":
             factor = 1/25.4
         else:
-            print "Unsupported units:", units
+            log.error("Unsupported units: %s" % str(units))
             return 1.0
 
         self.units = units
@@ -293,7 +302,7 @@ class ApertureMacro:
                 self.primitives.append([eval(x) for x in elements])
                 continue
 
-            print "WARNING: Unknown syntax of aperture macro part:", part
+            log.warning("Unknown syntax of aperture macro part: %s" % str(part))
 
     def append(self, data):
         """
@@ -834,7 +843,7 @@ class Gerber (Geometry):
                                     "modifiers": paramList}
             return apid
 
-        print "WARNING: Aperture not implemented:", apertureType
+        log.warning("Aperture not implemented: %s" % str(apertureType))
         return None
         
     def parse_file(self, filename):
@@ -975,7 +984,7 @@ class Gerber (Geometry):
                             geo = Polygon(path)
                         else:
                             if last_path_aperture is None:
-                                print "Warning: No aperture defined for curent path. (%d)" % line_num
+                                log.warning("No aperture defined for curent path. (%d)" % line_num)
                             width = self.apertures[last_path_aperture]["size"]
                             geo = LineString(path).buffer(width/2)
                         poly_buffer.append(geo)
@@ -1016,13 +1025,13 @@ class Gerber (Geometry):
                     j = 0
 
                 if quadrant_mode is None:
-                    print "ERROR: Found arc without preceding quadrant specification G74 or G75. (%d)" % line_num
-                    print gline
+                    log.error("Found arc without preceding quadrant specification G74 or G75. (%d)" % line_num)
+                    log.error(gline)
                     continue
 
                 if mode is None and current_interpolation_mode not in [2, 3]:
-                    print "ERROR: Found arc without circular interpolation mode defined. (%d)" % line_num
-                    print gline
+                    log.error("Found arc without circular interpolation mode defined. (%d)" % line_num)
+                    log.error(gline)
                     continue
                 elif mode is not None:
                     current_interpolation_mode = int(mode)
@@ -1033,10 +1042,10 @@ class Gerber (Geometry):
 
                 # Nothing created! Pen Up.
                 if current_operation_code == 2:
-                    print "Warning: Arc with D2. (%d)" % line_num
+                    log.warning("Arc with D2. (%d)" % line_num)
                     if len(path) > 1:
                         if last_path_aperture is None:
-                            print "Warning: No aperture defined for curent path. (%d)" % line_num
+                            log.warning("No aperture defined for curent path. (%d)" % line_num)
 
                         # --- BUFFERED ---
                         width = self.apertures[last_path_aperture]["size"]
@@ -1050,7 +1059,7 @@ class Gerber (Geometry):
 
                 # Flash should not happen here
                 if current_operation_code == 3:
-                    print "ERROR: Trying to flash within arc. (%d)" % line_num
+                    log.error("Trying to flash within arc. (%d)" % line_num)
                     continue
 
                 if quadrant_mode == 'MULTI':
@@ -1075,7 +1084,7 @@ class Gerber (Geometry):
                     continue
 
                 if quadrant_mode == 'SINGLE':
-                    print "Warning: Single quadrant arc are not implemented yet. (%d)" % line_num
+                    log.warning("Single quadrant arc are not implemented yet. (%d)" % line_num)
 
             ### Operation code alone
             match = self.opcode_re.search(gline)
@@ -1228,7 +1237,7 @@ class Gerber (Geometry):
                 continue
 
             ### Line did not match any pattern. Warn user.
-            print "WARNING: Line ignored (%d):" % line_num, gline
+            log.warning("Line ignored (%d): %s" % (line_num, gline))
         
         if len(path) > 1:
             # EOF, create shapely LineString if something still in path
@@ -1528,7 +1537,7 @@ class Excellon(Geometry):
                         y = current_y
 
                     if x is None or y is None:
-                        print "ERROR: Missing coordinates"
+                        log.error("Missing coordinates")
                         continue
 
                     self.drills.append({'point': Point((x, y)), 'tool': current_tool})
@@ -1550,7 +1559,7 @@ class Excellon(Geometry):
                         y = current_y
 
                     if x is None or y is None:
-                        print "ERROR: Missing coordinates"
+                        log.error("Missing coordinates")
                         continue
 
                     self.drills.append({'point': Point((x, y)), 'tool': current_tool})
@@ -1581,7 +1590,7 @@ class Excellon(Geometry):
                     self.units = {"INCH": "IN", "METRIC": "MM"}[match.group(1)]
                     continue
 
-            print "WARNING: Line ignored:", eline
+            log.warning("Line ignored: %s" % eline)
         
     def parse_number(self, number_str):
         """
@@ -1724,7 +1733,7 @@ class CNCjob(Geometry):
 
     def convert_units(self, units):
         factor = Geometry.convert_units(self, units)
-        print "CNCjob.convert_units()"
+        log.debug("CNCjob.convert_units()")
 
         self.z_cut *= factor
         self.z_move *= factor
@@ -1783,20 +1792,20 @@ class CNCjob(Geometry):
         :return: None
         :rtype: None
         """
-        print "Creating CNC Job from Excellon..."
+        log.debug("Creating CNC Job from Excellon...")
         if tools == "all":
             tools = [tool for tool in exobj.tools]
         else:
             tools = [x.strip() for x in tools.split(",")]
             tools = filter(lambda i: i in exobj.tools, tools)
-        print "Tools are:", tools
+        log.debug("Tools are: %s" % str(tools))
 
         points = []
         for drill in exobj.drills:
             if drill['tool'] in tools:
                 points.append(drill['point'])
 
-        print "Found %d drills." % len(points)
+        log.debug("Found %d drills." % len(points))
         #self.kind = "drill"
         self.gcode = []
 
@@ -1873,8 +1882,8 @@ class CNCjob(Geometry):
                     self.gcode += self.polygon2gcode(poly, tolerance=tolerance)
                 continue
 
-            print "WARNING: G-code generation not implemented for %s" % (str(type(geo)))
-        
+            log.warning("G-code generation not implemented for %s" % (str(type(geo))))
+
         self.gcode += "G00 Z%.4f\n" % self.z_move  # Stop cutting
         self.gcode += "G00 X0Y0\n"
         self.gcode += "M05\n"  # Spindle stop
@@ -1967,8 +1976,8 @@ class CNCjob(Geometry):
             ## Changing height
             if 'Z' in gobj:
                 if ('X' in gobj or 'Y' in gobj) and gobj['Z'] != current['Z']:
-                    print "WARNING: Non-orthogonal motion: From", current
-                    print "         To:", gobj
+                    log.warning("Non-orthogonal motion: From %s" % str(current))
+                    log.warning("  To: %s" % str(gobj))
                 current['Z'] = gobj['Z']
                 # Store the path into geometry and reset path
                 if len(path) > 1:
@@ -2239,7 +2248,7 @@ def get_bounds(geometry_list):
             xmax = max([xmax, gxmax])
             ymax = max([ymax, gymax])
         except:
-            print "DEV WARNING: Tried to get bounds of empty geometry."
+            log.warning("DEVELOPMENT: Tried to get bounds of empty geometry.")
 
     return [xmin, ymin, xmax, ymax]
 
@@ -2392,7 +2401,7 @@ def plotg(geo):
             _ = iter(g)
             plotg(g)
         except:
-            print "Cannot plot:", str(type(g))
+            log.error("Cannot plot: " + str(type(g)))
             continue
 
 

+ 1 - 1
defaults.json

@@ -1 +1 @@
-{"gerber_noncopperrounded": false, "geometry_paintoverlap": 0.15, "geometry_plot": true, "excellon_feedrate": 5.0, "gerber_plot": 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}
+{"gerber_noncopperrounded": false, "geometry_paintoverlap": 0.15, "geometry_plot": true, "excellon_feedrate": 5.0, "gerber_plot": 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, "gerber_noncoppermargin": 0.0}

+ 1 - 1
recent.json

@@ -1 +1 @@
-[{"kind": "project", "filename": "C:\\Users\\jpcaram\\Dropbox\\VNA\\KiCad_Bridge2\\Bridge2.fcproj"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\Kenney\\Project Outputs for AnalogPredistortion1\\apd.TXT"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\Kenney\\Project Outputs for AnalogPredistortion1\\apd.GTL"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\VNA\\KiCad_Bridge2\\KiCad_Bridge2.drl"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\LockController_v1.0_pcb-RoundHoles.TXT\\LockController_v1.0_pcb-RoundHoles.TXT"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\VNA\\KiCad_Bridge2\\KiCad_Bridge2-F_Cu.gtl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\WindMills - Bottom Copper 2.gbr"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\Example1_copper_bottom_Gndplane_modified.gbr"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\Example1_copper_bottom_Gndplane.gbr"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\CC_LOAD_7000164-00_REV_A_copper_top.gbr"}]
+[{"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\bedini 7 coils capacitor discharge.gbr"}, {"kind": "cncjob", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\test.cnc"}, {"kind": "project", "filename": "C:\\Users\\jpcaram\\Dropbox\\VNA\\KiCad_Bridge2\\Bridge2_test.fcproj"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\Controller_Board_Shield\\CBS-B_Cu.gbl"}, {"kind": "cncjob", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\squarerpcb\\KiCad_Squarer.gcode"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\VNA\\KiCad_Squarer\\KiCad_Squarer.drl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\VNA\\KiCad_Squarer\\KiCad_Squarer-F_Cu.gtl"}, {"kind": "project", "filename": "C:\\Users\\jpcaram\\Dropbox\\VNA\\KiCad_Bridge2\\Bridge2.fcproj"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\Kenney\\Project Outputs for AnalogPredistortion1\\apd.TXT"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\Kenney\\Project Outputs for AnalogPredistortion1\\apd.GTL"}]

Неке датотеке нису приказане због велике количине промена