Browse Source

Use of logging instead of print statements.

Juan Pablo Caram 11 năm trước cách đây
mục cha
commit
582e472e12
10 tập tin đã thay đổi với 730 bổ sung611 xóa
  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>

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 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"}]

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác