فهرست منبع

Merged marius_stanciu/flatcam_beta/Beta_8.994 into Beta_unstable

Marius Stanciu 5 سال پیش
والد
کامیت
0104adcbe1

+ 19 - 0
CHANGELOG.md

@@ -7,6 +7,25 @@ CHANGELOG for FlatCAM beta
 
 =================================================
 
+7.06.2020
+
+- refactoring in camlib.py. Made sure that some conditions are met, if some of the parameters are None then return failure. Modifications in generate_from_geometry_2 and generate_from_multitool_geometry methods
+- fixed issue with trying to access GUI from different threads by adding a new signal for printing to shell messages
+- fixed a small issue in Gerber file opener filter that did not see the *.TOP extension or *.outline extension
+- in Excellon parser added a way to "guestimate" the units if no units are detected in the header. I may need to make it optional in Preferences
+- changed the Excellon defaults for zeros suppression to TZ (assumed that most Excellon without units in header will come out of older Eagle) and the Excellon export default is now with coordinates in decimal
+- made sure that the message that exclusion areas are deleted is displayed only if there are shapes in the exclusion areas storage
+- fixed bug: on first ever usage of FlatCAM beta the last loaded language (alphabetically) is used instead of English (in current state is Russian)
+- made sure the the GUI settings are cleared on each new install
+- added a new signal that is triggered by change in visibility for the Shell Dock and will change the status of the shell label in the status bar. In this way the label will really be changed each time the shell is toggled
+- optimized the GUI in Film Tool
+- optimized GUI in Alignment Tool
+
+6.06.2020
+
+- NCC Tool - added a message to warn the user that he needs at least one tool with clearing operation
+- added a GUI element in the Preferences to control the possibility to edit with mouse cursor objects in the Project Tab. It is named: "Allow Edit"
+
 5.06.2020
 
 - fixed a small issue in the Panelization Tool that blocked the usage of a Geometry object as panelization reference

+ 4 - 1
Common.py

@@ -565,11 +565,12 @@ class ExclusionAreas(QtCore.QObject):
         :return:    None
         :rtype:
         """
+        if self.exclusion_areas_storage:
+            self.app.inform.emit('%s' % _("All exclusion zones deleted."))
         self.exclusion_areas_storage.clear()
         AppTool.delete_moving_selection_shape(self)
         self.app.delete_selection_shape()
         AppTool.delete_tool_selection_shape(self, shapes_storage=self.exclusion_shapes)
-        self.app.inform.emit('%s' % _("All exclusion zones deleted."))
 
     def delete_sel_shapes(self, idxs):
         """
@@ -604,6 +605,7 @@ class ExclusionAreas(QtCore.QObject):
         if self.app.is_legacy is True:
             self.exclusion_shapes.redraw()
 
+        # if there are still some exclusion areas in the storage
         if self.exclusion_areas_storage:
             self.app.inform.emit('[success] %s' % _("Selected exclusion zones deleted."))
         else:
@@ -618,6 +620,7 @@ class ExclusionAreas(QtCore.QObject):
                                             """)
             self.cnc_button.setToolTip('%s' % _("Generate the CNC Job object."))
 
+            # there are no more exclusion areas in the storage, all have been selected and deleted
             self.app.inform.emit('%s' % _("All exclusion zones deleted."))
 
     def travel_coordinates(self, start_point, end_point, tooldia):

+ 35 - 22
appGUI/MainGUI.py

@@ -1705,6 +1705,8 @@ class MainGUI(QtWidgets.QMainWindow):
         self.geom_update[int, int, int, int, int].connect(self.save_geometry)
         self.final_save.connect(self.app.final_save)
 
+        self.shell_dock.visibilityChanged.connect(self.on_shelldock_toggled)
+
     def save_geometry(self, x, y, width, height, notebook_width):
         """
         Will save the application geometry and positions in the defaults dicitionary to be restored at the next
@@ -1823,7 +1825,12 @@ class MainGUI(QtWidgets.QMainWindow):
             subprocess.Popen(['xdg-open', self.app.data_path])
         self.app.inform.emit('[success] %s' % _("FlatCAM Preferences Folder opened."))
 
-    def on_gui_clear(self):
+    def on_gui_clear(self, signal=None, forced_clear=False):
+        """
+        Will clear the settings that are stored in QSettings.
+        """
+        log.debug("Clearing the settings in QSettings. GUI settings cleared.")
+
         theme_settings = QtCore.QSettings("Open Source", "FlatCAM")
         theme_settings.setValue('theme', 'white')
 
@@ -1831,20 +1838,22 @@ class MainGUI(QtWidgets.QMainWindow):
 
         resource_loc = self.app.resource_location
 
-        msgbox = QtWidgets.QMessageBox()
-        msgbox.setText(_("Are you sure you want to delete the GUI Settings? \n"))
-        msgbox.setWindowTitle(_("Clear GUI Settings"))
-        msgbox.setWindowIcon(QtGui.QIcon(resource_loc + '/trash32.png'))
-        msgbox.setIcon(QtWidgets.QMessageBox.Question)
+        response = None
+        if forced_clear is False:
+            msgbox = QtWidgets.QMessageBox()
+            msgbox.setText(_("Are you sure you want to delete the GUI Settings? \n"))
+            msgbox.setWindowTitle(_("Clear GUI Settings"))
+            msgbox.setWindowIcon(QtGui.QIcon(resource_loc + '/trash32.png'))
+            msgbox.setIcon(QtWidgets.QMessageBox.Question)
 
-        bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
-        bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
+            bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
+            bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
 
-        msgbox.setDefaultButton(bt_no)
-        msgbox.exec_()
-        response = msgbox.clickedButton()
+            msgbox.setDefaultButton(bt_no)
+            msgbox.exec_()
+            response = msgbox.clickedButton()
 
-        if response == bt_yes:
+        if forced_clear is True or response == bt_yes:
             qsettings = QSettings("Open Source", "FlatCAM")
             for key in qsettings.allKeys():
                 qsettings.remove(key)
@@ -3664,18 +3673,8 @@ class MainGUI(QtWidgets.QMainWindow):
         if self.shell_dock.isVisible():
             self.shell_dock.hide()
             self.app.plotcanvas.native.setFocus()
-            self.shell_status_label.setStyleSheet("")
-            self.app.inform[str, bool].emit(_("Shell disabled."), False)
         else:
             self.shell_dock.show()
-            self.shell_status_label.setStyleSheet("""
-                                                  QLabel
-                                                  {
-                                                      color: black;
-                                                      background-color: lightcoral;
-                                                  }
-                                                  """)
-            self.app.inform[str, bool].emit(_("Shell enabled."), False)
 
             # I want to take the focus and give it to the Tcl Shell when the Tcl Shell is run
             # self.shell._edit.setFocus()
@@ -3691,6 +3690,20 @@ class MainGUI(QtWidgets.QMainWindow):
             #                       no_km)
             # QtWidgets.qApp.sendEvent(self.shell._edit, f)
 
+    def on_shelldock_toggled(self, visibility):
+        if visibility is True:
+            self.shell_status_label.setStyleSheet("""
+                                                  QLabel
+                                                  {
+                                                      color: black;
+                                                      background-color: lightcoral;
+                                                  }
+                                                  """)
+            self.app.inform[str, bool].emit(_("Shell enabled."), False)
+        else:
+            self.shell_status_label.setStyleSheet("")
+            self.app.inform[str, bool].emit(_("Shell disabled."), False)
+
 
 class ShortcutsTab(QtWidgets.QWidget):
 

+ 2 - 1
appGUI/preferences/PreferencesUIManager.py

@@ -91,7 +91,7 @@ class PreferencesUIManager:
             "global_proj_item_dis_color": self.ui.general_defaults_form.general_gui_group.proj_color_dis_entry,
             "global_project_autohide": self.ui.general_defaults_form.general_gui_group.project_autohide_cb,
 
-            # General GUI Settings
+            # General APP Settings
             "global_gridx": self.ui.general_defaults_form.general_app_set_group.gridx_entry,
             "global_gridy": self.ui.general_defaults_form.general_app_set_group.gridy_entry,
             "global_snap_max": self.ui.general_defaults_form.general_app_set_group.snap_max_dist_entry,
@@ -107,6 +107,7 @@ class PreferencesUIManager:
             "global_pan_button": self.ui.general_defaults_form.general_app_set_group.pan_button_radio,
             "global_mselect_key": self.ui.general_defaults_form.general_app_set_group.mselect_radio,
             "global_delete_confirmation": self.ui.general_defaults_form.general_app_set_group.delete_conf_cb,
+            "global_allow_edit_in_project_tab": self.ui.general_defaults_form.general_app_set_group.allow_edit_cb,
             "global_open_style": self.ui.general_defaults_form.general_app_set_group.open_style_cb,
             "global_toggle_tooltips": self.ui.general_defaults_form.general_app_set_group.toggle_tooltips_cb,
             "global_machinist_setting": self.ui.general_defaults_form.general_app_set_group.machinist_cb,

+ 15 - 7
appGUI/preferences/general/GeneralAPPSetGroupUI.py

@@ -384,6 +384,14 @@ class GeneralAPPSetGroupUI(OptionsGroupUI):
         )
         grid0.addWidget(self.delete_conf_cb, 30, 0, 1, 2)
 
+        self.allow_edit_cb = FCCheckBox(_("Allow Edit"))
+        self.allow_edit_cb.setToolTip(
+            _("When cheched, the user can edit the objects in the Project Tab\n"
+              "by using the left mouse button click on the object name.\n"
+              "Active after restart.")
+        )
+        grid0.addWidget(self.allow_edit_cb, 31, 0, 1, 2)
+
         # Open behavior
         self.open_style_cb = FCCheckBox('%s' % _('"Open" behavior'))
         self.open_style_cb.setToolTip(
@@ -393,7 +401,7 @@ class GeneralAPPSetGroupUI(OptionsGroupUI):
               "path for saving files or the path for opening files.")
         )
 
-        grid0.addWidget(self.open_style_cb, 31, 0, 1, 2)
+        grid0.addWidget(self.open_style_cb, 32, 0, 1, 2)
 
         # Enable/Disable ToolTips globally
         self.toggle_tooltips_cb = FCCheckBox(label=_('Enable ToolTips'))
@@ -402,7 +410,7 @@ class GeneralAPPSetGroupUI(OptionsGroupUI):
               "when hovering with mouse over items throughout the App.")
         )
 
-        grid0.addWidget(self.toggle_tooltips_cb, 32, 0, 1, 2)
+        grid0.addWidget(self.toggle_tooltips_cb, 33, 0, 1, 2)
 
         # Machinist settings that allow unsafe settings
         self.machinist_cb = FCCheckBox(_("Allow Machinist Unsafe Settings"))
@@ -414,7 +422,7 @@ class GeneralAPPSetGroupUI(OptionsGroupUI):
               "<<WARNING>>: Don't change this unless you know what you are doing !!!")
         )
 
-        grid0.addWidget(self.machinist_cb, 33, 0, 1, 2)
+        grid0.addWidget(self.machinist_cb, 34, 0, 1, 2)
 
         # Bookmarks Limit in the Help Menu
         self.bm_limit_spinner = FCSpinner()
@@ -426,8 +434,8 @@ class GeneralAPPSetGroupUI(OptionsGroupUI):
               "but the menu will hold only so much.")
         )
 
-        grid0.addWidget(self.bm_limit_label, 34, 0)
-        grid0.addWidget(self.bm_limit_spinner, 34, 1)
+        grid0.addWidget(self.bm_limit_label, 35, 0)
+        grid0.addWidget(self.bm_limit_spinner, 35, 1)
 
         # Activity monitor icon
         self.activity_label = QtWidgets.QLabel('%s:' % _("Activity Icon"))
@@ -437,8 +445,8 @@ class GeneralAPPSetGroupUI(OptionsGroupUI):
         self.activity_combo = FCComboBox()
         self.activity_combo.addItems(['Ball black', 'Ball green', 'Arrow green', 'Eclipse green'])
 
-        grid0.addWidget(self.activity_label, 35, 0)
-        grid0.addWidget(self.activity_combo, 35, 1)
+        grid0.addWidget(self.activity_label, 36, 0)
+        grid0.addWidget(self.activity_combo, 36, 1)
 
         self.layout.addStretch()
 

+ 2 - 2
appObjects/AppObject.py

@@ -137,9 +137,9 @@ class AppObject(QtCore.QObject):
             return "fail"
 
         t2 = time.time()
-        msg = "%f seconds executing initialize()." % (t2 - t1)
+        msg = "New object with name: %s. %f seconds executing initialize()." % (name, (t2 - t1))
         log.debug(msg)
-        self.app.shell_message(msg)
+        self.app.inform_shell.emit(msg)
 
         if return_value == 'fail':
             log.debug("Object (%s) parsing and/or geometry creation failed." % kind)

+ 7 - 2
appObjects/ObjectCollection.py

@@ -50,11 +50,10 @@ class KeySensitiveListView(QtWidgets.QTreeView):
     def __init__(self, app, parent=None):
         super(KeySensitiveListView, self).__init__(parent)
         self.setHeaderHidden(True)
-        # self.setEditTriggers(QtWidgets.QTreeView.SelectedClicked)     # allow Edit on Tree
-        self.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers)
 
         # self.setRootIsDecorated(False)
         # self.setExpandsOnDoubleClick(False)
+        self.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers)    # No edit in the Project Tab Tree
 
         # Enable dragging and dropping onto the appGUI
         self.setAcceptDrops(True)
@@ -309,6 +308,12 @@ class ObjectCollection(QtCore.QAbstractItemModel):
 
         self.view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
         self.view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
+
+        if self.app.defaults["global_allow_edit_in_project_tab"] is True:
+            self.view.setEditTriggers(QtWidgets.QTreeView.SelectedClicked)  # allow Edit on Tree
+        else:
+            self.view.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers)
+
         # self.view.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)
         # self.view.setDragEnabled(True)
         # self.view.setAcceptDrops(True)

+ 32 - 5
appParsers/ParseExcellon.py

@@ -115,12 +115,12 @@ class Excellon(Geometry):
 
         # ## IN|MM -> Units are inherited from Geometry
         self.units = self.app.defaults['units']
+        self.units_found = self.app.defaults['units']
 
         # Trailing "T" or leading "L" (default)
         # self.zeros = "T"
         self.zeros = zeros or self.defaults["zeros"]
         self.zeros_found = deepcopy(self.zeros)
-        self.units_found = deepcopy(self.units)
 
         # this will serve as a default if the Excellon file has no info regarding of tool diameters (this info may be
         # in another file like for PCB WIzard ECAD software
@@ -134,6 +134,8 @@ class Excellon(Geometry):
         self.excellon_format_upper_mm = excellon_format_upper_mm or self.defaults["excellon_format_upper_mm"]
         self.excellon_format_lower_mm = excellon_format_lower_mm or self.defaults["excellon_format_lower_mm"]
         self.excellon_units = excellon_units or self.defaults["excellon_units"]
+        self.excellon_units_found = None
+
         # detected Excellon format is stored here:
         self.excellon_format = None
 
@@ -362,7 +364,7 @@ class Excellon(Geometry):
 
                         self.excellon_format_upper_in = match.group(1)
                         self.excellon_format_lower_in = match.group(2)
-                        log.warning("Altium Excellon format preset found in comments: %s:%s" %
+                        log.warning("Excellon format preset found in comments: %s:%s" %
                                     (match.group(1), match.group(2)))
                         continue
                     else:
@@ -384,6 +386,25 @@ class Excellon(Geometry):
                         if allegro_warning is True:
                             name_tool = 0
                         log.warning("Found end of the header: %s" % eline)
+
+                        '''
+                        In case that the units were not found in the header, we have two choices:
+                        - one is to use the default value in the App Preferences
+                        - the other is to make an evaluation based on a threshold
+                        we process here the self.tools list and make a list with tools with diameter less or equal 
+                        with 0.1 and a list with tools with value greater than 0.1, 0.1 being the threshold value. 
+                        Most tools in Excellon are greater than 0.1mm therefore if most of the tools are under this 
+                        value it is safe to assume that the units are in INCH
+                        '''
+                        greater_tools = set()
+                        lower_tools = set()
+                        if not self.excellon_units_found and self.tools:
+                            for tool in self.tools:
+                                tool_dia = float(self.tools[tool]['C'])
+                                lower_tools.add(tool_dia) if tool_dia <= 0.1 else greater_tools.add(tool_dia)
+
+                            assumed_units = "IN" if len(lower_tools) > len(greater_tools) else "MM"
+                            self.units = assumed_units
                         continue
 
                 # ## Alternative units format M71/M72
@@ -802,6 +823,8 @@ class Excellon(Geometry):
                     match = self.units_re.match(eline)
                     if match:
                         self.units = {"METRIC": "MM", "INCH": "IN"}[match.group(1)]
+                        self.excellon_units_found = self.units
+
                         self.zeros = match.group(2)  # "T" or "L". Might be empty
                         self.excellon_format = match.group(3)
                         if self.excellon_format:
@@ -815,9 +838,9 @@ class Excellon(Geometry):
                                 self.excellon_format_lower_in = lower
 
                         # Modified for issue #80
-                        log.warning("UNITS found inline before conversion: %s" % self.units)
+                        log.warning("UNITS found inline - Value before conversion: %s" % self.units)
                         self.convert_units(self.units)
-                        log.warning("UNITS found inline after conversion: %s" % self.units)
+                        log.warning("UNITS found inline - Value after conversion: %s" % self.units)
                         if self.units == 'MM':
                             log.warning("Excellon format preset is: %s:%s" %
                                         (str(self.excellon_format_upper_mm), str(self.excellon_format_lower_mm)))
@@ -836,6 +859,7 @@ class Excellon(Geometry):
                         log.warning("Type of UNITS found inline, in header, after conversion: %s" % self.units)
                         log.warning("Excellon format preset is: %s:%s" %
                                     (str(self.excellon_format_upper_in), str(self.excellon_format_lower_in)))
+                        self.excellon_units_found = "IN"
                         continue
                     elif "METRIC" in eline:
                         line_units = "MM"
@@ -845,6 +869,7 @@ class Excellon(Geometry):
                         log.warning("Type of UNITS found inline, in header, after conversion: %s" % self.units)
                         log.warning("Excellon format preset is: %s:%s" %
                                     (str(self.excellon_format_upper_mm), str(self.excellon_format_lower_mm)))
+                        self.excellon_units_found = "MM"
                         continue
 
                     # Search for zeros type again because it might be alone on the line
@@ -857,7 +882,9 @@ class Excellon(Geometry):
                 # ## Units and number format outside header# ##
                 match = self.units_re.match(eline)
                 if match:
-                    self.units = self.units = {"METRIC": "MM", "INCH": "IN"}[match.group(1)]
+                    self.units = {"METRIC": "MM", "INCH": "IN"}[match.group(1)]
+                    self.excellon_units_found = self.units
+
                     self.zeros = match.group(2)  # "T" or "L". Might be empty
                     self.excellon_format = match.group(3)
                     if self.excellon_format:

+ 4 - 7
appTools/ToolAlignObjects.py

@@ -72,7 +72,7 @@ class AlignObjects(AppTool):
         self.type_obj_radio = RadioSet([
             {"label": _("Gerber"), "value": "grb"},
             {"label": _("Excellon"), "value": "exc"},
-        ], orientation='vertical', stretch=False)
+        ])
 
         grid0.addWidget(self.type_obj_radio, 3, 0, 1, 2)
 
@@ -95,7 +95,7 @@ class AlignObjects(AppTool):
 
         grid0.addWidget(QtWidgets.QLabel(''), 6, 0, 1, 2)
 
-        self.aligned_label = QtWidgets.QLabel('<b>%s:</b>' % _("TARGET object"))
+        self.aligned_label = QtWidgets.QLabel('<b>%s:</b>' % _("DESTINATION object"))
         self.aligned_label.setToolTip(
             _("Specify the type of object to be aligned to.\n"
               "It can be of type: Gerber or Excellon.\n"
@@ -108,7 +108,7 @@ class AlignObjects(AppTool):
         self.type_aligner_obj_radio = RadioSet([
             {"label": _("Gerber"), "value": "grb"},
             {"label": _("Excellon"), "value": "exc"},
-        ], orientation='vertical', stretch=False)
+        ])
 
         grid0.addWidget(self.type_aligner_obj_radio, 8, 0, 1, 2)
 
@@ -142,10 +142,7 @@ class AlignObjects(AppTool):
             [
                 {'label': _('Single Point'), 'value': 'sp'},
                 {'label': _('Dual Point'), 'value': 'dp'}
-            ],
-            orientation='vertical',
-            stretch=False
-        )
+            ])
 
         grid0.addWidget(self.a_type_lbl, 12, 0, 1, 2)
         grid0.addWidget(self.a_type_radio, 13, 0, 1, 2)

+ 23 - 18
appTools/ToolFilm.py

@@ -9,7 +9,7 @@ from PyQt5 import QtCore, QtWidgets
 
 from appTool import AppTool
 from appGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, \
-    OptionalHideInputSection, OptionalInputSection, FCComboBox, FCFileSaveDialog
+    OptionalHideInputSection, FCComboBox, FCFileSaveDialog
 
 from copy import deepcopy
 import logging
@@ -68,7 +68,7 @@ class Film(AppTool):
         self.tf_type_obj_combo = RadioSet([{'label': _('Gerber'), 'value': 'grb'},
                                            {'label': _('Geometry'), 'value': 'geo'}])
 
-        self.tf_type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Object Type"))
+        self.tf_type_obj_combo_label = QtWidgets.QLabel('<b>%s</b>:' % _("Object"))
         self.tf_type_obj_combo_label.setToolTip(
             _("Specify the type of object for which to create the film.\n"
               "The object can be of type: Gerber or Geometry.\n"
@@ -107,13 +107,6 @@ class Film(AppTool):
         self.tf_box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.tf_box_combo.is_last = True
 
-        # self.tf_box_combo_label = QtWidgets.QLabel('%s:' % _("Box Object"))
-        # self.tf_box_combo_label.setToolTip(
-        #     _("The actual object that is used as container for the\n "
-        #       "selected object for which we create the film.\n"
-        #       "Usually it is the PCB outline but it can be also the\n"
-        #       "same object for which the film is created.")
-        # )
         grid0.addWidget(self.tf_box_combo, 3, 0, 1, 2)
 
         separator_line = QtWidgets.QFrame()
@@ -160,8 +153,13 @@ class Film(AppTool):
         grid0.addWidget(self.film_scaley_label, 8, 0)
         grid0.addWidget(self.film_scaley_entry, 8, 1)
 
-        self.ois_scale = OptionalInputSection(self.film_scale_cb, [self.film_scalex_label, self.film_scalex_entry,
-                                                                   self.film_scaley_label,  self.film_scaley_entry])
+        self.ois_scale = OptionalHideInputSection(self.film_scale_cb,
+                                                  [
+                                                      self.film_scalex_label,
+                                                      self.film_scalex_entry,
+                                                      self.film_scaley_label,
+                                                      self.film_scaley_entry
+                                                  ])
 
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
@@ -214,9 +212,15 @@ class Film(AppTool):
         grid0.addWidget(self.film_skew_ref_label, 13, 0)
         grid0.addWidget(self.film_skew_reference, 13, 1)
 
-        self.ois_skew = OptionalInputSection(self.film_skew_cb, [self.film_skewx_label, self.film_skewx_entry,
-                                                                 self.film_skewy_label,  self.film_skewy_entry,
-                                                                 self.film_skew_reference])
+        self.ois_skew = OptionalHideInputSection(self.film_skew_cb,
+                                                 [
+                                                     self.film_skewx_label,
+                                                     self.film_skewx_entry,
+                                                     self.film_skewy_label,
+                                                     self.film_skewy_entry,
+                                                     self.film_skew_ref_label,
+                                                     self.film_skew_reference
+                                                 ])
 
         separator_line1 = QtWidgets.QFrame()
         separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
@@ -245,8 +249,11 @@ class Film(AppTool):
         grid0.addWidget(self.film_mirror_axis_label, 16, 0)
         grid0.addWidget(self.film_mirror_axis, 16, 1)
 
-        self.ois_mirror = OptionalInputSection(self.film_mirror_cb,
-                                               [self.film_mirror_axis_label, self.film_mirror_axis])
+        self.ois_mirror = OptionalHideInputSection(self.film_mirror_cb,
+                                                   [
+                                                       self.film_mirror_axis_label,
+                                                       self.film_mirror_axis
+                                                   ])
 
         separator_line2 = QtWidgets.QFrame()
         separator_line2.setFrameShape(QtWidgets.QFrame.HLine)
@@ -272,8 +279,6 @@ class Film(AppTool):
         grid0.addWidget(self.film_scale_stroke_label, 19, 0)
         grid0.addWidget(self.film_scale_stroke_entry, 19, 1)
 
-        grid0.addWidget(QtWidgets.QLabel(''), 20, 0)
-
         # Film Type
         self.film_type = RadioSet([{'label': _('Positive'), 'value': 'pos'},
                                    {'label': _('Negative'), 'value': 'neg'}],

+ 3 - 3
appTools/ToolIsolation.py

@@ -2083,14 +2083,14 @@ class ToolIsolation(AppTool, Gerber):
             self.app.inform.emit("[WARNING] %s" % _("Partial failure. The geometry was processed with all tools.\n"
                                                     "But there are still not-isolated geometry elements. "
                                                     "Try to include a tool with smaller diameter."))
-            self.app.shell_message(msg=_("The following are coordinates for the copper features "
-                                         "that could not be isolated:"))
+            msg = _("The following are coordinates for the copper features that could not be isolated:")
+            self.app.inform_shell.emit(msg)
             msg = ''
             for geo in work_geo:
                 pt = geo.representative_point()
                 coords = '(%s, %s), ' % (str(pt.x), str(pt.y))
                 msg += coords
-            self.app.shell_message(msg=msg)
+            self.app.inform_shell.emit(msg=msg)
 
     def combined_normal(self, iso_obj, iso2geo, tools_storage, lim_area, negative_dia=None, plot=True):
         """

+ 16 - 23
appTools/ToolNCC.py

@@ -766,6 +766,7 @@ class NonCopperClear(AppTool, Gerber):
         try:
             current_uid = int(self.tools_table.item(current_row, 3).text())
             self.ncc_tools[current_uid]['data']['tools_nccoperation'] = val
+            # TODO got a crash here, a KeyError exception; need to see it again and find out the why
         except AttributeError:
             return
 
@@ -1639,6 +1640,7 @@ class NonCopperClear(AppTool, Gerber):
                                                                     "use a number."))
                         continue
 
+                # find out which tools is for isolation and which are for copper clearing
                 for uid_k, uid_v in self.ncc_tools.items():
                     if round(uid_v['tooldia'], self.decimals) == round(self.tooldia, self.decimals):
                         if uid_v['data']['tools_nccoperation'] == "iso":
@@ -2300,23 +2302,30 @@ class NonCopperClear(AppTool, Gerber):
             self.app.inform.emit('[ERROR_NOTCL] %s' % _('Geometry could not be cleared completely'))
             return None
 
-    def clear_copper(self, ncc_obj, sel_obj=None, ncctooldia=None, isotooldia=None, outname=None, order=None,
+    def clear_copper(self, ncc_obj, ncctooldia, isotooldia, sel_obj=None, outname=None, order=None,
                      tools_storage=None, run_threaded=True):
         """
         Clear the excess copper from the entire object.
 
         :param ncc_obj:         ncc cleared object
+        :type ncc_obj:          appObjects.FlatCAMGerber.GerberObject
+        :param ncctooldia:      a list of diameters of the tools to be used to ncc clear
+        :type ncctooldia:       list
+        :param isotooldia:      a list of diameters of the tools to be used for isolation
+        :type isotooldia:       list
         :param sel_obj:
-        :param ncctooldia:      a tuple or single element made out of diameters of the tools to be used to ncc clear
-        :param isotooldia:      a tuple or single element made out of diameters of the tools to be used for isolation
+        :type sel_obj:
         :param outname:         name of the resulting object
+        :type outname:          str
         :param order:           Tools order
         :param tools_storage:   whether to use the current tools_storage self.ncc_tools or a different one.
                                 Usage of the different one is related to when this function is called
                                 from a TcL command.
+        :type tools_storage:    dict
 
         :param run_threaded:    If True the method will be run in a threaded way suitable for GUI usage; if False
                                 it will run non-threaded for TclShell usage
+        :type run_threaded:     bool
         :return:
         """
         log.debug("Executing the handler ...")
@@ -2338,29 +2347,13 @@ class NonCopperClear(AppTool, Gerber):
 
         # determine if to use the progressive plotting
         prog_plot = True if self.app.defaults["tools_ncc_plotting"] == 'progressive' else False
-        tools_storage = tools_storage if tools_storage is not None else self.ncc_tools
 
-        # ######################################################################################################
-        # # Read the tooldia parameter and create a sorted list out them - they may be more than one diameter ##
-        # ######################################################################################################
-        sorted_clear_tools = []
-        if ncctooldia is not None:
-            try:
-                sorted_clear_tools = [float(eval(dia)) for dia in ncctooldia.split(",") if dia != '']
-            except AttributeError:
-                if not isinstance(ncctooldia, list):
-                    sorted_clear_tools = [float(ncctooldia)]
-                else:
-                    sorted_clear_tools = ncctooldia
-        else:
-            # for row in range(self.tools_table.rowCount()):
-            #     if self.tools_table.cellWidget(row, 1).currentText() == 'clear_op':
-            #         sorted_clear_tools.append(float(self.tools_table.item(row, 1).text()))
-            for tooluid in self.ncc_tools:
-                if self.ncc_tools[tooluid]['data']['tools_nccoperation'] == 'clear':
-                    sorted_clear_tools.append(self.ncc_tools[tooluid]['tooldia'])
+        tools_storage = tools_storage if tools_storage is not None else self.ncc_tools
+        sorted_clear_tools = ncctooldia
 
         if not sorted_clear_tools:
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _("There is no copper clearing tool in the selection "
+                                                        "and at least one is needed."))
             return 'fail'
 
         # ########################################################################################################

+ 1 - 1
appTranslation.py

@@ -135,7 +135,7 @@ def apply_language(domain, lang=None):
         if settings.contains("language"):
             name = settings.value('language')
         else:
-            name = settings.value('English')
+            name = 'English'
             # in case the 'language' parameter is not in QSettings add it to QSettings and it's value is
             # the default language, English
             settings.setValue('language', 'English')

+ 29 - 10
app_Main.py

@@ -162,9 +162,9 @@ class App(QtCore.QObject):
     # ###############################################################################################################
     # ################################### Version and VERSION DATE ##################################################
     # ###############################################################################################################
-    # version = "Unstable Version"
-    version = 8.993
-    version_date = "2020/06/05"
+    version = "Unstable Version"
+    # version = 8.994
+    version_date = "2020/07/05"
     beta = True
 
     engine = '3D'
@@ -203,9 +203,10 @@ class App(QtCore.QObject):
     # ###############################################################################################################
 
     # Inform the user
-    # Handled by:
-    #  * App.info() --> Print on the status bar
+    # Handled by: App.info() --> Print on the status bar
     inform = QtCore.pyqtSignal([str], [str, bool])
+    # Handled by: App.info_shell() --> Print on the shell
+    inform_shell = QtCore.pyqtSignal(str)
 
     app_quit = QtCore.pyqtSignal()
 
@@ -448,6 +449,17 @@ class App(QtCore.QObject):
         # ###########################################################################################################
         self.pool = Pool()
 
+        # ###########################################################################################################
+        # ###################################### Clear GUI Settings - once at first start ###########################
+        # ###########################################################################################################
+        if self.defaults["first_run"] is True:
+            # on first run clear the previous QSettings, therefore clearing the GUI settings
+            qsettings = QSettings("Open Source", "FlatCAM")
+            for key in qsettings.allKeys():
+                qsettings.remove(key)
+            # This will write the setting to the platform specific storage.
+            del qsettings
+
         # ###########################################################################################################
         # ###################################### Setting the Splash Screen ##########################################
         # ###########################################################################################################
@@ -578,7 +590,7 @@ class App(QtCore.QObject):
         # ################################ It's done only once after install   #####################################
         # ###########################################################################################################
         if self.defaults["first_run"] is True:
-            # ONLY AT FIRST STARTUP INIT THE GUI LAYOUT TO 'COMPACT'
+            # ONLY AT FIRST STARTUP INIT THE GUI LAYOUT TO 'minimal'
             initial_lay = 'minimal'
             self.ui.general_defaults_form.general_gui_group.on_layout(lay=initial_lay)
 
@@ -762,6 +774,9 @@ class App(QtCore.QObject):
         self.inform[str].connect(self.info)
         self.inform[str, bool].connect(self.info)
 
+        # signal for displaying messages in the shell
+        self.inform_shell.connect(self.info_shell)
+
         # signal to be called when the app is quiting
         self.app_quit.connect(self.quit_application, type=Qt.QueuedConnection)
         self.message.connect(lambda: message_dialog(parent=self.ui))
@@ -2428,6 +2443,9 @@ class App(QtCore.QObject):
             if msg != '' and shell_echo is True:
                 self.shell_message(msg)
 
+    def info_shell(self, msg):
+        self.shell_message(msg=msg)
+
     def on_import_preferences(self):
         """
         Loads the application default settings from a saved file into
@@ -6699,10 +6717,11 @@ class App(QtCore.QObject):
         self.defaults.report_usage("on_fileopengerber")
         App.log.debug("on_fileopengerber()")
 
-        _filter_ = "Gerber Files (*.gbr *.ger *.gtl *.gbl *.gts *.gbs *.gtp *.gbp *.gto *.gbo *.gm1 *.gml *.gm3 *" \
-                   ".gko *.cmp *.sol *.stc *.sts *.plc *.pls *.crc *.crs *.tsm *.bsm *.ly2 *.ly15 *.dim *.mil *.grb" \
-                   "*.top *.bot *.smt *.smb *.sst *.ssb *.spt *.spb *.pho *.gdo *.art *.gbd);;" \
-                   "Protel Files (*.gtl *.gbl *.gts *.gbs *.gto *.gbo *.gtp *.gbp *.gml *.gm1 *.gm3 *.gko);;" \
+        _filter_ = "Gerber Files (*.gbr *.ger *.gtl *.gbl *.gts *.gbs *.gtp *.gbp *.gto *.gbo *.gm1 *.gml *.gm3 " \
+                   "*.gko *.cmp *.sol *.stc *.sts *.plc *.pls *.crc *.crs *.tsm *.bsm *.ly2 *.ly15 *.dim *.mil *.grb " \
+                   "*.top *.bot *.smt *.smb *.sst *.ssb *.spt *.spb *.pho *.gdo *.art *.gbd *.outline);;" \
+                   "Protel Files (*.gtl *.gbl *.gts *.gbs *.gto *.gbo *.gtp *.gbp *.gml *.gm1 *.gm3 *.gko " \
+                   "*.outline);;" \
                    "Eagle Files (*.cmp *.sol *.stc *.sts *.plc *.pls *.crc *.crs *.tsm *.bsm *.ly2 *.ly15 *.dim " \
                    "*.mil);;" \
                    "OrCAD Files (*.top *.bot *.smt *.smb *.sst *.ssb *.spt *.spb);;" \

+ 52 - 64
camlib.py

@@ -3745,23 +3745,28 @@ class CNCjob(Geometry):
         flat_geometry = self.flatten(temp_solid_geometry, pathonly=True)
         log.debug("%d paths" % len(flat_geometry))
 
-        self.tooldia = float(tooldia) if tooldia else None
+        try:
+            self.tooldia = float(tooldia)
+        except Exception as e:
+            self.app.inform.emit('[ERROR] %s\n%s' % (_("Failed."), str(e)))
+            return 'fail'
+
         self.z_cut = float(z_cut) if z_cut else None
         self.z_move = float(z_move) if z_move is not None else None
 
-        self.feedrate = float(feedrate) if feedrate else None
-        self.z_feedrate = float(feedrate_z) if feedrate_z is not None else None
-        self.feedrate_rapid = float(feedrate_rapid) if feedrate_rapid else None
+        self.feedrate = float(feedrate) if feedrate else  self.app.defaults["geometry_feedrate"]
+        self.z_feedrate = float(feedrate_z) if feedrate_z is not None else  self.app.defaults["geometry_feedrate_z"]
+        self.feedrate_rapid = float(feedrate_rapid) if feedrate_rapid else  self.app.defaults["geometry_feedrate_rapid"]
 
         self.spindlespeed = int(spindlespeed) if spindlespeed != 0 else None
         self.spindledir = spindledir
         self.dwell = dwell
-        self.dwelltime = float(dwelltime) if dwelltime else None
+        self.dwelltime = float(dwelltime) if dwelltime else  self.app.defaults["geometry_dwelltime"]
 
-        self.startz = float(startz) if startz is not None else None
-        self.z_end = float(endz) if endz is not None else None
+        self.startz = float(startz) if startz is not None else  self.app.defaults["geometry_startz"]
+        self.z_end = float(endz) if endz is not None else  self.app.defaults["geometry_endz"]
 
-        self.xy_end = re.sub('[()\[\]]', '', str(endxy)) if endxy else None
+        self.xy_end = re.sub('[()\[\]]', '', str(endxy)) if endxy else  self.app.defaults["geometry_endxy"]
 
         if self.xy_end and self.xy_end != '':
             self.xy_end = [float(eval(a)) for a in self.xy_end.split(",")]
@@ -3771,10 +3776,10 @@ class CNCjob(Geometry):
                                                    "in the format (x, y) but now there is only one value, not two."))
             return 'fail'
 
-        self.z_depthpercut = float(depthpercut) if depthpercut else None
+        self.z_depthpercut = float(depthpercut) if depthpercut else self.app.defaults["geometry_depthperpass"]
         self.multidepth = multidepth
 
-        self.z_toolchange = float(toolchangez) if toolchangez is not None else None
+        self.z_toolchange = float(toolchangez) if toolchangez is not None else self.app.defaults["geometry_toolchangez"]
 
         # it servers in the preprocessor file
         self.tool = tool_no
@@ -3783,7 +3788,8 @@ class CNCjob(Geometry):
             if toolchangexy == '':
                 self.xy_toolchange = None
             else:
-                self.xy_toolchange = re.sub('[()\[\]]', '', str(toolchangexy)) if toolchangexy else None
+                self.xy_toolchange = re.sub('[()\[\]]', '', str(toolchangexy)) \
+                    if toolchangexy else self.app.defaults["geometry_toolchangexy"]
 
                 if self.xy_toolchange and self.xy_toolchange != '':
                     self.xy_toolchange = [float(eval(a)) for a in self.xy_toolchange.split(",")]
@@ -3802,9 +3808,9 @@ class CNCjob(Geometry):
 
         if self.z_cut is None:
             if 'laser' not in self.pp_geometry_name:
-                self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                     _("Cut_Z parameter is None or zero. Most likely a bad combinations of "
-                                       "other parameters."))
+                self.app.inform.emit(
+                    '[ERROR_NOTCL] %s' % _("Cut_Z parameter is None or zero. Most likely a bad combinations of "
+                                           "other parameters."))
                 return 'fail'
             else:
                 self.z_cut = 0
@@ -3959,9 +3965,7 @@ class CNCjob(Geometry):
                     total_cut = total_cut + geo.length
 
                     self.gcode += self.create_gcode_single_pass(geo, current_tooldia, extracut, extracut_length,
-                                                                tolerance,
-                                                                z_move=z_move, postproc=p,
-                                                                old_point=current_pt)
+                                                                tolerance, z_move=z_move, old_point=current_pt)
 
                 # --------- Multi-pass ---------
                 else:
@@ -3976,8 +3980,7 @@ class CNCjob(Geometry):
                     total_cut += (geo.length * nr_cuts)
 
                     self.gcode += self.create_gcode_multi_pass(geo, current_tooldia, extracut, extracut_length,
-                                                               tolerance,
-                                                               z_move=z_move, postproc=p,
+                                                               tolerance,  z_move=z_move, postproc=p,
                                                                old_point=current_pt)
 
                 # calculate the total distance
@@ -4009,14 +4012,12 @@ class CNCjob(Geometry):
         )
         return self.gcode
 
-    def generate_from_geometry_2(
-            self, geometry, append=True, tooldia=None, offset=0.0, tolerance=0, z_cut=None, z_move=None,
-            feedrate=None, feedrate_z=None, feedrate_rapid=None,
-            spindlespeed=None, spindledir='CW', dwell=False, dwelltime=None,
-            multidepth=False, depthpercut=None,
-            toolchange=False, toolchangez=None, toolchangexy="0.0, 0.0",
-            extracut=False, extracut_length=None, startz=None, endz=None, endxy='',
-            pp_geometry_name=None, tool_no=1):
+    def generate_from_geometry_2(self, geometry, append=True, tooldia=None, offset=0.0, tolerance=0, z_cut=None,
+                                 z_move=None, feedrate=None, feedrate_z=None, feedrate_rapid=None, spindlespeed=None,
+                                 spindledir='CW', dwell=False, dwelltime=None, multidepth=False, depthpercut=None,
+                                 toolchange=False, toolchangez=None, toolchangexy="0.0, 0.0", extracut=False,
+                                 extracut_length=None, startz=None, endz=None, endxy='', pp_geometry_name=None,
+                                 tool_no=1):
         """
         Second algorithm to generate from Geometry.
 
@@ -4126,7 +4127,7 @@ class CNCjob(Geometry):
         flat_geometry = self.flatten(temp_solid_geometry, pathonly=True)
         log.debug("%d paths" % len(flat_geometry))
 
-        default_dia = 0.01
+        default_dia = None
         if isinstance(self.app.defaults["geometry_cnctooldia"], float):
             default_dia = self.app.defaults["geometry_cnctooldia"]
         else:
@@ -4142,6 +4143,10 @@ class CNCjob(Geometry):
         except ValueError:
             self.tooldia = [float(el) for el in tooldia.split(',') if el != ''] if tooldia is not None else default_dia
 
+        if self.tooldia is None:
+            self.app.inform.emit('[ERROR] %s' % _("Failed."))
+            return 'fail'
+
         self.z_cut = float(z_cut) if z_cut is not None else self.app.defaults["geometry_cutz"]
         self.z_move = float(z_move) if z_move is not None else self.app.defaults["geometry_travelz"]
 
@@ -4186,11 +4191,9 @@ class CNCjob(Geometry):
                     self.xy_toolchange = [float(eval(a)) for a in self.xy_toolchange.split(",")]
 
                 if len(self.xy_toolchange) < 2:
-                    self.app.inform.emit(
-                        '[ERROR] %s' %
-                        _("The Toolchange X,Y field in Edit -> Preferences has to be in the format (x, y) \n"
-                          "but now there is only one value, not two. ")
-                    )
+                    msg = _("The Toolchange X,Y field in Edit -> Preferences has to be in the format (x, y)\n"
+                            "but now there is only one value, not two.")
+                    self.app.inform.emit('[ERROR] %s' % msg)
                     return 'fail'
         except Exception as e:
             log.debug("camlib.CNCJob.generate_from_geometry_2() --> %s" % str(e))
@@ -4369,9 +4372,7 @@ class CNCjob(Geometry):
                     # calculate the cut distance
                     total_cut += geo.length
                     self.gcode += self.create_gcode_single_pass(geo, current_tooldia, extracut, self.extracut_length,
-                                                                tolerance,
-                                                                z_move=z_move, postproc=p,
-                                                                old_point=current_pt)
+                                                                tolerance, z_move=z_move, old_point=current_pt)
 
                 # --------- Multi-pass ---------
                 else:
@@ -4386,8 +4387,7 @@ class CNCjob(Geometry):
                     total_cut += (geo.length * nr_cuts)
 
                     self.gcode += self.create_gcode_multi_pass(geo, current_tooldia, extracut, self.extracut_length,
-                                                               tolerance,
-                                                               z_move=z_move, postproc=p,
+                                                               tolerance, z_move=z_move, postproc=p,
                                                                old_point=current_pt)
 
                 # calculate the travel distance
@@ -4611,8 +4611,7 @@ class CNCjob(Geometry):
             gcode += self.doformat(p.lift_code)
         return gcode
 
-    def create_gcode_single_pass(self, geometry, cdia, extracut, extracut_length, tolerance, z_move, postproc,
-                                 old_point=(0, 0)):
+    def create_gcode_single_pass(self, geometry, cdia, extracut, extracut_length, tolerance, z_move, old_point=(0, 0)):
         """
         # G-code. Note: self.linear2gcode() and self.point2gcode() will lower and raise the tool every time.
 
@@ -4628,8 +4627,6 @@ class CNCjob(Geometry):
         :type tolerance:            float
         :param z_move:              Travel Z
         :type z_move:               float
-        :param postproc:            Preprocessor class
-        :type postproc:             class
         :param old_point:           Previous point
         :type old_point:            tuple
         :return:                    Gcode
@@ -4638,19 +4635,15 @@ class CNCjob(Geometry):
         # p = postproc
 
         if type(geometry) == LineString or type(geometry) == LinearRing:
-            if extracut is False:
-                gcode_single_pass = self.linear2gcode(geometry, z_move=z_move, dia=cdia, tolerance=tolerance,
+            if extracut is False or not geometry.is_ring:
+                gcode_single_pass = self.linear2gcode(geometry, cdia, z_move=z_move, tolerance=tolerance,
                                                       old_point=old_point)
             else:
-                if geometry.is_ring:
-                    gcode_single_pass = self.linear2gcode_extra(geometry, extracut_length, tolerance=tolerance,
-                                                                z_move=z_move, dia=cdia,
-                                                                old_point=old_point)
-                else:
-                    gcode_single_pass = self.linear2gcode(geometry, tolerance=tolerance, z_move=z_move, dia=cdia,
-                                                          old_point=old_point)
+                gcode_single_pass = self.linear2gcode_extra(geometry, cdia, extracut_length, tolerance=tolerance,
+                                                            z_move=z_move, old_point=old_point)
+
         elif type(geometry) == Point:
-            gcode_single_pass = self.point2gcode(geometry, dia=cdia, z_move=z_move, old_point=old_point)
+            gcode_single_pass = self.point2gcode(geometry, cdia, z_move=z_move, old_point=old_point)
         else:
             log.warning("G-code generation not implemented for %s" % (str(type(geometry))))
             return
@@ -4708,22 +4701,17 @@ class CNCjob(Geometry):
             # at the first point if the tool is down (in the material).  So, an extra G00 should show up but
             # is inconsequential.
             if type(geometry) == LineString or type(geometry) == LinearRing:
-                if extracut is False:
-                    gcode_multi_pass += self.linear2gcode(geometry, tolerance=tolerance, z_cut=depth, up=False,
-                                                          z_move=z_move, dia=cdia, old_point=old_point)
+                if extracut is False or not geometry.is_ring:
+                    gcode_multi_pass += self.linear2gcode(geometry, cdia, tolerance=tolerance, z_cut=depth, up=False,
+                                                          z_move=z_move, old_point=old_point)
                 else:
-                    if geometry.is_ring:
-                        gcode_multi_pass += self.linear2gcode_extra(geometry, extracut_length, tolerance=tolerance,
-                                                                    dia=cdia, z_move=z_move, z_cut=depth, up=False,
-                                                                    old_point=old_point)
-                    else:
-                        gcode_multi_pass += self.linear2gcode(geometry, tolerance=tolerance, z_cut=depth, up=False,
-                                                              dia=cdia, z_move=z_move,
-                                                              old_point=old_point)
+                    gcode_multi_pass += self.linear2gcode_extra(geometry, cdia, extracut_length, tolerance=tolerance,
+                                                                z_move=z_move, z_cut=depth, up=False,
+                                                                old_point=old_point)
 
             # Ignore multi-pass for points.
             elif type(geometry) == Point:
-                gcode_multi_pass += self.point2gcode(geometry, dia=cdia, z_move=z_move, old_point=old_point)
+                gcode_multi_pass += self.point2gcode(geometry, cdia, z_move=z_move, old_point=old_point)
                 break  # Ignoring ...
             else:
                 log.warning("G-code generation not implemented for %s" % (str(type(geometry))))

+ 74 - 65
defaults.py

@@ -22,70 +22,15 @@ log = logging.getLogger('FlatCAMDefaults')
 class FlatCAMDefaults:
 
     factory_defaults = {
-        # Global APP Preferences
-        "decimals_inch": 4,
-        "decimals_metric": 4,
-        "version": 8.992,   # defaults format version, not necessarily equal to app version
+        # Global
+        "version": 8.992,  # defaults format version, not necessarily equal to app version
         "first_run": True,
-        "units": "MM",
         "root_folder_path": '',
         "global_serial": 0,
         "global_stats": dict(),
         "global_tabs_detachable": True,
         "global_jump_ref": 'abs',
         "global_locate_pt": 'bl',
-        "global_tpdf_tmargin": 15.0,
-        "global_tpdf_bmargin": 10.0,
-        "global_tpdf_lmargin": 20.0,
-        "global_tpdf_rmargin": 20.0,
-        "global_autosave": False,
-        "global_autosave_timeout": 300000,
-
-        # General
-        "global_graphic_engine": '3D',
-        "global_hud": True,
-        "global_app_level": 'b',
-        "global_portable": False,
-        "global_language": 'English',
-        "global_version_check": True,
-        "global_send_stats": True,
-        "global_pan_button": '2',
-        "global_mselect_key": 'Control',
-        "global_project_at_startup": False,
-        "global_systray_icon": True,
-        "global_project_autohide": True,
-        "global_toggle_tooltips": True,
-        "global_worker_number": 2,
-        "global_tolerance": 0.005,
-        "global_open_style": True,
-        "global_delete_confirmation": True,
-        "global_compression_level": 3,
-        "global_save_compressed": True,
-
-        "global_machinist_setting": False,
-
-        # Global GUI Preferences
-        "global_gridx": 1.0,
-        "global_gridy": 1.0,
-        "global_snap_max": 0.05,
-        "global_workspace": False,
-        "global_workspaceT": "A4",
-        "global_workspace_orientation": 'p',
-
-        "global_grid_context_menu": {
-            'in': [0.01, 0.02, 0.025, 0.05, 0.1],
-            'mm': [0.1, 0.2, 0.5, 1, 2.54]
-        },
-
-        "global_sel_fill": '#a5a5ffbf',
-        "global_sel_line": '#0000ffbf',
-        "global_alt_sel_fill": '#BBF268BF',
-        "global_alt_sel_line": '#006E20BF',
-        "global_draw_color": '#FF0000',
-        "global_sel_draw_color": '#0000FF',
-        "global_proj_item_color": '#000000',
-        "global_proj_item_dis_color": '#b7b7cb',
-        "global_activity_icon": 'Ball green',
 
         "global_toolbar_view": 511,
 
@@ -95,6 +40,12 @@ class FlatCAMDefaults:
         # 1 = show trace(show trace always),
         # 2 = (For the future).
 
+        "global_grid_context_menu": {
+            'in': [0.01, 0.02, 0.025, 0.05, 0.1],
+            'mm': [0.1, 0.2, 0.5, 1, 2.54]
+        },
+        "global_hud": True,
+
         # Persistence
         "global_last_folder": None,
         "global_last_save_folder": None,
@@ -109,12 +60,8 @@ class FlatCAMDefaults:
         # Constants...
         "global_defaults_save_period_ms": 20000,  # Time between default saves.
         "global_shell_shape": [500, 300],  # Shape of the shell in pixels.
-        "global_shell_at_startup": False,  # Show the shell at startup.
         "global_recent_limit": 10,  # Max. items in recent list.
 
-        "global_bookmarks": dict(),
-        "global_bookmarks_limit": 10,
-
         "fit_key": 'V',
         "zoom_out_key": '-',
         "zoom_in_key": '=',
@@ -125,18 +72,80 @@ class FlatCAMDefaults:
 
         "global_tcl_path": '',
 
-        # General GUI Settings
+        # General APP Preferences
+        "units": "MM",
+        "decimals_inch": 4,
+        "decimals_metric": 4,
+        "global_graphic_engine": '3D',
+        "global_app_level": 'b',
+
+        "global_portable": False,
+        "global_language": 'English',
+
+
+        "global_systray_icon": True,
+        "global_shell_at_startup": False,  # Show the shell at startup.
+        "global_project_at_startup": False,
+        "global_version_check": True,
+        "global_send_stats": True,
+        "global_worker_number": 2,
+        "global_tolerance": 0.005,
+
+        "global_save_compressed": True,
+        "global_compression_level": 3,
+        "global_autosave": False,
+        "global_autosave_timeout": 300000,
+
+        "global_tpdf_tmargin": 15.0,
+        "global_tpdf_bmargin": 10.0,
+        "global_tpdf_lmargin": 20.0,
+        "global_tpdf_rmargin": 20.0,
+
+        # General GUI Preferences
         "global_theme": 'white',
         "global_gray_icons": False,
+
+        "global_layout": "compact",
         "global_hover": False,
         "global_selection_shape": True,
-        "global_layout": "compact",
+
+        "global_sel_fill": '#a5a5ffbf',
+        "global_sel_line": '#0000ffbf',
+        "global_alt_sel_fill": '#BBF268BF',
+        "global_alt_sel_line": '#006E20BF',
+        "global_draw_color": '#FF0000',
+        "global_sel_draw_color": '#0000FF',
+        "global_proj_item_color": '#000000',
+        "global_proj_item_dis_color": '#b7b7cb',
+        "global_project_autohide": True,
+
+        # General App Settings
+        "global_gridx": 1.0,
+        "global_gridy": 1.0,
+        "global_snap_max": 0.05,
+
+        "global_workspace": False,
+        "global_workspaceT": "A4",
+        "global_workspace_orientation": 'p',
+
         "global_cursor_type": "small",
         "global_cursor_size": 20,
         "global_cursor_width": 2,
         "global_cursor_color": '#FF0000',
         "global_cursor_color_enabled": True,
 
+        "global_pan_button": '2',
+        "global_mselect_key": 'Control',
+
+        "global_delete_confirmation": True,
+        "global_allow_edit_in_project_tab": False,
+        "global_open_style": True,
+        "global_toggle_tooltips": True,
+        "global_machinist_setting": False,
+        "global_bookmarks": dict(),
+        "global_bookmarks_limit": 10,
+        "global_activity_icon": 'Ball green',
+
         # Gerber General
         "gerber_plot": True,
         "gerber_solid": True,
@@ -218,7 +227,7 @@ class FlatCAMDefaults:
         "excellon_format_lower_in": 4,
         "excellon_format_upper_mm": 3,
         "excellon_format_lower_mm": 3,
-        "excellon_zeros": "L",
+        "excellon_zeros": "T",
         "excellon_units": "INCH",
         "excellon_update": True,
 
@@ -273,7 +282,7 @@ class FlatCAMDefaults:
 
         # Excellon Export
         "excellon_exp_units": 'INCH',
-        "excellon_exp_format": 'ndec',
+        "excellon_exp_format": 'dec',
         "excellon_exp_integer": 2,
         "excellon_exp_decimals": 4,
         "excellon_exp_zeros": 'LZ',

+ 1 - 1
tclCommands/TclCommandSetOrigin.py

@@ -100,4 +100,4 @@ class TclCommandSetOrigin(TclCommand):
         self.app.on_set_zero_click(event=None, location=loc, noplot=True, use_thread=False)
         msg = '[success] Tcl %s: %s' % (_('Origin set by offsetting all loaded objects with '),
                                         '{0:.4f}, {0:.4f}'.format(loc[0], loc[1]))
-        self.app.shell_message(msg, success=True, show=False)
+        self.app.inform_shell.emit(msg)

+ 4 - 6
tclCommands/TclCommandSetPath.py

@@ -79,18 +79,16 @@ class TclCommandSetPath(TclCommand):
                     "The provided path",
                     str(path),
                     "is a path to file and not a directory as expected.")
-                self.app.shell_message(msg, success=True, show=False)
+                self.app.inform_shell.emit(msg)
                 return "Failed. The Tcl command set_path was used but it was not a directory."
             else:
                 msg = '[ERROR] %s: %s, %s' % (
-                    "The provided path",
-                    str(path),
-                    "do not exist. Check for typos.")
-                self.app.shell_message(msg, success=True, show=False)
+                    "The provided path", str(path), "do not exist. Check for typos.")
+                self.app.inform_shell.emit(msg)
                 return "Failed. The Tcl command set_path was used but it does not exist."
 
         cd_command = 'cd %s' % path
         self.app.shell.exec_command(cd_command, no_echo=False)
         self.app.defaults["global_tcl_path"] = str(path)
         msg = '[success] %s: %s' % ("Relative path set to", str(path))
-        self.app.shell_message(msg, success=True, show=False)
+        self.app.inform_shell.emit(msg)