Przeglądaj źródła

Merged in marius_stanciu/flatcam_beta/Beta (pull request #184)

Beta
Marius Stanciu 6 lat temu
rodzic
commit
43dcdef14a
41 zmienionych plików z 3823 dodań i 2484 usunięć
  1. 352 115
      FlatCAMApp.py
  2. 17 12
      FlatCAMObj.py
  3. 2 0
      FlatCAMWorker.py
  4. 1 1
      ObjectCollection.py
  5. 68 0
      README.md
  6. 38 9
      camlib.py
  7. 1 1
      config/configuration.txt
  8. 19 13
      flatcamEditors/FlatCAMExcEditor.py
  9. 89 93
      flatcamEditors/FlatCAMGeoEditor.py
  10. 64 55
      flatcamEditors/FlatCAMGrbEditor.py
  11. 331 89
      flatcamGUI/FlatCAMGUI.py
  12. 126 49
      flatcamGUI/GUIElements.py
  13. 127 166
      flatcamGUI/ObjectUI.py
  14. 20 14
      flatcamTools/ToolCalculators.py
  15. 207 104
      flatcamTools/ToolCutOut.py
  16. 13 8
      flatcamTools/ToolDblSided.py
  17. 11 6
      flatcamTools/ToolFilm.py
  18. 9 4
      flatcamTools/ToolImage.py
  19. 3 3
      flatcamTools/ToolMeasurement.py
  20. 312 92
      flatcamTools/ToolNonCopperClear.py
  21. 289 48
      flatcamTools/ToolPaint.py
  22. 20 15
      flatcamTools/ToolPanelize.py
  23. 13 8
      flatcamTools/ToolPcbWizard.py
  24. 48 20
      flatcamTools/ToolProperties.py
  25. 26 21
      flatcamTools/ToolSolderPaste.py
  26. 67 44
      flatcamTools/ToolSub.py
  27. 22 16
      flatcamTools/ToolTransform.py
  28. BIN
      locale/de/LC_MESSAGES/strings.mo
  29. 207 201
      locale/de/LC_MESSAGES/strings.po
  30. BIN
      locale/en/LC_MESSAGES/strings.mo
  31. 213 207
      locale/en/LC_MESSAGES/strings.po
  32. BIN
      locale/es/LC_MESSAGES/strings.mo
  33. 207 201
      locale/es/LC_MESSAGES/strings.po
  34. BIN
      locale/pt_BR/LC_MESSAGES/strings.mo
  35. 207 201
      locale/pt_BR/LC_MESSAGES/strings.po
  36. BIN
      locale/ro/LC_MESSAGES/strings.mo
  37. 213 207
      locale/ro/LC_MESSAGES/strings.po
  38. BIN
      locale/ru/LC_MESSAGES/strings.mo
  39. 212 206
      locale/ru/LC_MESSAGES/strings.po
  40. 268 254
      locale_template/strings.pot
  41. 1 1
      tclCommands/TclCommandFollow.py

+ 352 - 115
FlatCAMApp.py

@@ -98,8 +98,8 @@ class App(QtCore.QObject):
     # ####################################
     # ####################################
     # Version and VERSION DATE ###########
     # Version and VERSION DATE ###########
     # ####################################
     # ####################################
-    version = 8.95
-    version_date = "2019/08/17"
+    version = 8.96
+    version_date = "2019/08/23"
     beta = True
     beta = True
 
 
     # current date now
     # current date now
@@ -183,6 +183,9 @@ class App(QtCore.QObject):
     # in the worker task.
     # in the worker task.
     thread_exception = QtCore.pyqtSignal(object)
     thread_exception = QtCore.pyqtSignal(object)
 
 
+    # used to signal that there are arguments for the app
+    args_at_startup = QtCore.pyqtSignal()
+
     def __init__(self, user_defaults=True, post_gui=None):
     def __init__(self, user_defaults=True, post_gui=None):
         """
         """
         Starts the application.
         Starts the application.
@@ -320,7 +323,6 @@ class App(QtCore.QObject):
 
 
         QtCore.QObject.__init__(self)
         QtCore.QObject.__init__(self)
         self.ui = FlatCAMGUI(self.version, self.beta, self)
         self.ui = FlatCAMGUI(self.version, self.beta, self)
-        self.set_ui_title(name=_("New Project - Not saved"))
 
 
         self.ui.geom_update[int, int, int, int, int].connect(self.save_geometry)
         self.ui.geom_update[int, int, int, int, int].connect(self.save_geometry)
         self.ui.final_save.connect(self.final_save)
         self.ui.final_save.connect(self.final_save)
@@ -345,6 +347,7 @@ class App(QtCore.QObject):
             # General App
             # General App
             "units": self.ui.general_defaults_form.general_app_group.units_radio,
             "units": self.ui.general_defaults_form.general_app_group.units_radio,
             "global_app_level": self.ui.general_defaults_form.general_app_group.app_level_radio,
             "global_app_level": self.ui.general_defaults_form.general_app_group.app_level_radio,
+            "global_portable": self.ui.general_defaults_form.general_app_group.portability_cb,
             "global_language": self.ui.general_defaults_form.general_app_group.language_cb,
             "global_language": self.ui.general_defaults_form.general_app_group.language_cb,
 
 
             "global_shell_at_startup": self.ui.general_defaults_form.general_app_group.shell_startup_cb,
             "global_shell_at_startup": self.ui.general_defaults_form.general_app_group.shell_startup_cb,
@@ -420,6 +423,21 @@ class App(QtCore.QObject):
 
 
             # Gerber Editor
             # Gerber Editor
             "gerber_editor_sel_limit": self.ui.gerber_defaults_form.gerber_editor_group.sel_limit_entry,
             "gerber_editor_sel_limit": self.ui.gerber_defaults_form.gerber_editor_group.sel_limit_entry,
+            "gerber_editor_newcode": self.ui.gerber_defaults_form.gerber_editor_group.addcode_entry,
+            "gerber_editor_newsize": self.ui.gerber_defaults_form.gerber_editor_group.addsize_entry,
+            "gerber_editor_newtype": self.ui.gerber_defaults_form.gerber_editor_group.addtype_combo,
+            "gerber_editor_newdim": self.ui.gerber_defaults_form.gerber_editor_group.adddim_entry,
+            "gerber_editor_array_size": self.ui.gerber_defaults_form.gerber_editor_group.grb_array_size_entry,
+            "gerber_editor_lin_axis": self.ui.gerber_defaults_form.gerber_editor_group.grb_axis_radio,
+            "gerber_editor_lin_pitch": self.ui.gerber_defaults_form.gerber_editor_group.grb_pitch_entry,
+            "gerber_editor_lin_angle": self.ui.gerber_defaults_form.gerber_editor_group.grb_angle_entry,
+            "gerber_editor_circ_dir": self.ui.gerber_defaults_form.gerber_editor_group.grb_circular_dir_radio,
+            "gerber_editor_circ_angle":
+                self.ui.gerber_defaults_form.gerber_editor_group.grb_circular_angle_entry,
+            "gerber_editor_scale_f": self.ui.gerber_defaults_form.gerber_editor_group.grb_scale_entry,
+            "gerber_editor_buff_f": self.ui.gerber_defaults_form.gerber_editor_group.grb_buff_entry,
+            "gerber_editor_ma_low": self.ui.gerber_defaults_form.gerber_editor_group.grb_ma_low_entry,
+            "gerber_editor_ma_high": self.ui.gerber_defaults_form.gerber_editor_group.grb_ma_high_entry,
 
 
             # Excellon General
             # Excellon General
             "excellon_plot": self.ui.excellon_defaults_form.excellon_gen_group.plot_cb,
             "excellon_plot": self.ui.excellon_defaults_form.excellon_gen_group.plot_cb,
@@ -558,6 +576,7 @@ class App(QtCore.QObject):
 
 
             # NCC Tool
             # NCC Tool
             "tools_ncctools": self.ui.tools_defaults_form.tools_ncc_group.ncc_tool_dia_entry,
             "tools_ncctools": self.ui.tools_defaults_form.tools_ncc_group.ncc_tool_dia_entry,
+            "tools_nccorder": self.ui.tools_defaults_form.tools_ncc_group.ncc_order_radio,
             "tools_nccoverlap": self.ui.tools_defaults_form.tools_ncc_group.ncc_overlap_entry,
             "tools_nccoverlap": self.ui.tools_defaults_form.tools_ncc_group.ncc_overlap_entry,
             "tools_nccmargin": self.ui.tools_defaults_form.tools_ncc_group.ncc_margin_entry,
             "tools_nccmargin": self.ui.tools_defaults_form.tools_ncc_group.ncc_margin_entry,
             "tools_nccmethod": self.ui.tools_defaults_form.tools_ncc_group.ncc_method_radio,
             "tools_nccmethod": self.ui.tools_defaults_form.tools_ncc_group.ncc_method_radio,
@@ -578,6 +597,7 @@ class App(QtCore.QObject):
 
 
             # Paint Area Tool
             # Paint Area Tool
             "tools_painttooldia": self.ui.tools_defaults_form.tools_paint_group.painttooldia_entry,
             "tools_painttooldia": self.ui.tools_defaults_form.tools_paint_group.painttooldia_entry,
+            "tools_paintorder": self.ui.tools_defaults_form.tools_paint_group.paint_order_radio,
             "tools_paintoverlap": self.ui.tools_defaults_form.tools_paint_group.paintoverlap_entry,
             "tools_paintoverlap": self.ui.tools_defaults_form.tools_paint_group.paintoverlap_entry,
             "tools_paintmargin": self.ui.tools_defaults_form.tools_paint_group.paintmargin_entry,
             "tools_paintmargin": self.ui.tools_defaults_form.tools_paint_group.paintmargin_entry,
             "tools_paintmethod": self.ui.tools_defaults_form.tools_paint_group.paintmethod_combo,
             "tools_paintmethod": self.ui.tools_defaults_form.tools_paint_group.paintmethod_combo,
@@ -681,8 +701,10 @@ class App(QtCore.QObject):
             # Global APP Preferences
             # Global APP Preferences
             "global_serial": 0,
             "global_serial": 0,
             "global_stats": {},
             "global_stats": {},
+            "global_tabs_detachable": True,
             "units": "IN",
             "units": "IN",
             "global_app_level": 'b',
             "global_app_level": 'b',
+            "global_portable": False,
             "global_language": 'English',
             "global_language": 'English',
             "global_version_check": True,
             "global_version_check": True,
             "global_send_stats": True,
             "global_send_stats": True,
@@ -791,6 +813,20 @@ class App(QtCore.QObject):
 
 
             # Gerber Editor
             # Gerber Editor
             "gerber_editor_sel_limit": 30,
             "gerber_editor_sel_limit": 30,
+            "gerber_editor_newcode": 10,
+            "gerber_editor_newsize": 0.8,
+            "gerber_editor_newtype": 'C',
+            "gerber_editor_newdim": "0.5, 0.5",
+            "gerber_editor_array_size": 5,
+            "gerber_editor_lin_axis": 'X',
+            "gerber_editor_lin_pitch": 1,
+            "gerber_editor_lin_angle": 0.0,
+            "gerber_editor_circ_dir": 'CW',
+            "gerber_editor_circ_angle": 0.0,
+            "gerber_editor_scale_f": 1.0,
+            "gerber_editor_buff_f": 0.1,
+            "gerber_editor_ma_low": 0.0,
+            "gerber_editor_ma_high": 1.0,
 
 
             # Excellon General
             # Excellon General
             "excellon_plot": True,
             "excellon_plot": True,
@@ -916,6 +952,7 @@ class App(QtCore.QObject):
             "cncjob_toolchange_macro_enable": False,
             "cncjob_toolchange_macro_enable": False,
 
 
             "tools_ncctools": "0.0393701, 0.019685",
             "tools_ncctools": "0.0393701, 0.019685",
+            "tools_nccorder": 'rev',
             "tools_nccoverlap": 0.015748,
             "tools_nccoverlap": 0.015748,
             "tools_nccmargin": 0.0393701,
             "tools_nccmargin": 0.0393701,
             "tools_nccmethod": "seed",
             "tools_nccmethod": "seed",
@@ -934,6 +971,7 @@ class App(QtCore.QObject):
             "tools_cutout_convexshape": False,
             "tools_cutout_convexshape": False,
 
 
             "tools_painttooldia": 0.023622,
             "tools_painttooldia": 0.023622,
+            "tools_paintorder": 'rev',
             "tools_paintoverlap": 0.015748,
             "tools_paintoverlap": 0.015748,
             "tools_paintmargin": 0.0,
             "tools_paintmargin": 0.0,
             "tools_paintmethod": "seed",
             "tools_paintmethod": "seed",
@@ -1290,13 +1328,8 @@ class App(QtCore.QObject):
         self.tools_form = None
         self.tools_form = None
         self.on_options_combo_change(0)  # Will show the initial form
         self.on_options_combo_change(0)  # Will show the initial form
 
 
-        # ### Define OBJECT COLLECTION ###
-        self.collection = ObjectCollection(self)
-        self.ui.project_tab_layout.addWidget(self.collection.view)
         # ################################
         # ################################
 
 
-        self.log.debug("Finished creating Object Collection.")
-
         # ### Initialize the color box's color in Preferences -> Global -> Color
         # ### Initialize the color box's color in Preferences -> Global -> Color
         # Init Plot Colors
         # Init Plot Colors
         self.ui.general_defaults_form.general_gui_group.pf_color_entry.set_value(self.defaults['global_plot_fill'])
         self.ui.general_defaults_form.general_gui_group.pf_color_entry.set_value(self.defaults['global_plot_fill'])
@@ -1368,46 +1401,52 @@ class App(QtCore.QObject):
             "background-color:%s" % str(self.defaults['cncjob_annotation_fontcolor'])[:7])
             "background-color:%s" % str(self.defaults['cncjob_annotation_fontcolor'])[:7])
         # ### End of Data ####
         # ### End of Data ####
 
 
-        # ### Plot Area ####
-        start_plot_time = time.time()   # debug
-        self.plotcanvas = PlotCanvas(self.ui.right_layout, self)
+        # ###############################################
+        # ############# SETUP Plot Area #################
+        # ###############################################
 
 
-        self.plotcanvas.vis_connect('mouse_move', self.on_mouse_move_over_plot)
-        self.plotcanvas.vis_connect('mouse_press', self.on_mouse_click_over_plot)
-        self.plotcanvas.vis_connect('mouse_release', self.on_mouse_click_release_over_plot)
-        self.plotcanvas.vis_connect('mouse_double_click', self.on_double_click_over_plot)
-
-        # Keys over plot enabled
-        self.plotcanvas.vis_connect('key_press', self.ui.keyPressEvent)
+        start_plot_time = time.time()   # debug
+        self.plotcanvas = None
+        self.app_cursor = None
+        self.hover_shapes = None
+        self.on_plotcanvas_setup()
+        end_plot_time = time.time()
+        self.log.debug("Finished Canvas initialization in %s seconds." % (str(end_plot_time - start_plot_time)))
 
 
         self.ui.splitter.setStretchFactor(1, 2)
         self.ui.splitter.setStretchFactor(1, 2)
 
 
-        # So it can receive key presses
-        self.plotcanvas.vispy_canvas.native.setFocus()
-
-        self.app_cursor = self.plotcanvas.new_cursor()
-        self.app_cursor.enabled = False
-
         # to use for tools like Measurement tool who depends on the event sources who are changed inside the Editors
         # to use for tools like Measurement tool who depends on the event sources who are changed inside the Editors
         # depending on from where those tools are called different actions can be done
         # depending on from where those tools are called different actions can be done
         self.call_source = 'app'
         self.call_source = 'app'
 
 
-        end_plot_time = time.time()
-        self.log.debug("Finished Canvas initialization in %s seconds." % (str(end_plot_time - start_plot_time)))
+        # ##############################################
+        # ######### SETUP OBJECT COLLECTION ############
+        # ##############################################
+
+        self.collection = ObjectCollection(self)
+        self.ui.project_tab_layout.addWidget(self.collection.view)
 
 
         # ### Adjust tabs width ## ##
         # ### Adjust tabs width ## ##
         # self.collection.view.setMinimumWidth(self.ui.options_scroll_area.widget().sizeHint().width() +
         # self.collection.view.setMinimumWidth(self.ui.options_scroll_area.widget().sizeHint().width() +
         #     self.ui.options_scroll_area.verticalScrollBar().sizeHint().width())
         #     self.ui.options_scroll_area.verticalScrollBar().sizeHint().width())
         self.collection.view.setMinimumWidth(290)
         self.collection.view.setMinimumWidth(290)
+        self.log.debug("Finished creating Object Collection.")
+
+        # ###############################################
+        # ############# Worker SETUP ####################
+        # ###############################################
 
 
-        # ### Worker ####
         if self.defaults["global_worker_number"]:
         if self.defaults["global_worker_number"]:
             self.workers = WorkerStack(workers_number=int(self.defaults["global_worker_number"]))
             self.workers = WorkerStack(workers_number=int(self.defaults["global_worker_number"]))
         else:
         else:
             self.workers = WorkerStack(workers_number=2)
             self.workers = WorkerStack(workers_number=2)
         self.worker_task.connect(self.workers.add_task)
         self.worker_task.connect(self.workers.add_task)
+        self.log.debug("Finished creating Workers crew.")
+
+        # ################################################
+        # ############### Signal handling ################
+        # ################################################
 
 
-        # ### Signal handling ###
         # ### Custom signals  ###
         # ### Custom signals  ###
         self.inform.connect(self.info)
         self.inform.connect(self.info)
         self.app_quit.connect(self.quit_application)
         self.app_quit.connect(self.quit_application)
@@ -1506,12 +1545,8 @@ class App(QtCore.QObject):
         self.ui.menuviewenable.triggered.connect(self.enable_all_plots)
         self.ui.menuviewenable.triggered.connect(self.enable_all_plots)
 
 
         self.ui.menuview_zoom_fit.triggered.connect(self.on_zoom_fit)
         self.ui.menuview_zoom_fit.triggered.connect(self.on_zoom_fit)
-        self.ui.menuview_zoom_in.triggered.connect(
-            lambda: self.plotcanvas.zoom(1 / float(self.defaults['global_zoom_ratio']))
-        )
-        self.ui.menuview_zoom_out.triggered.connect(
-            lambda: self.plotcanvas.zoom(float(self.defaults['global_zoom_ratio']))
-        )
+        self.ui.menuview_zoom_in.triggered.connect(self.on_zoom_in)
+        self.ui.menuview_zoom_out.triggered.connect(self.on_zoom_out)
 
 
         self.ui.menuview_toggle_code_editor.triggered.connect(self.on_toggle_code_editor)
         self.ui.menuview_toggle_code_editor.triggered.connect(self.on_toggle_code_editor)
         self.ui.menuview_toggle_fscreen.triggered.connect(self.on_fullscreen)
         self.ui.menuview_toggle_fscreen.triggered.connect(self.on_fullscreen)
@@ -1545,6 +1580,15 @@ class App(QtCore.QObject):
         # ToolBar signals
         # ToolBar signals
         self.connect_toolbar_signals()
         self.connect_toolbar_signals()
 
 
+        # Notebook signals
+        # make the right click on the notebook tab connect to a function
+        self.ui.notebook.setupContextMenu()
+        self.ui.notebook.addContextMenu(
+            _("Detachable Tabs"), self.on_notebook_tab_rmb_click,
+            initial_checked=self.defaults["global_tabs_detachable"])
+        # activate initial state
+        self.on_notebook_tab_rmb_click(self.defaults["global_tabs_detachable"])
+
         # Context Menu
         # Context Menu
         self.ui.popmenu_disable.triggered.connect(lambda: self.toggle_plots(self.collection.get_selected()))
         self.ui.popmenu_disable.triggered.connect(lambda: self.toggle_plots(self.collection.get_selected()))
         self.ui.popmenu_panel_toggle.triggered.connect(self.on_toggle_notebook)
         self.ui.popmenu_panel_toggle.triggered.connect(self.on_toggle_notebook)
@@ -1668,6 +1712,9 @@ class App(QtCore.QObject):
         self.ui.buttonFind.clicked.connect(self.handleFindGCode)
         self.ui.buttonFind.clicked.connect(self.handleFindGCode)
         self.ui.buttonReplace.clicked.connect(self.handleReplaceGCode)
         self.ui.buttonReplace.clicked.connect(self.handleReplaceGCode)
 
 
+        # portability changed
+        self.ui.general_defaults_form.general_app_group.portability_cb.stateChanged.connect(self.on_portable_checked)
+
         # Object list
         # Object list
         self.collection.view.activated.connect(self.on_row_activated)
         self.collection.view.activated.connect(self.on_row_activated)
 
 
@@ -1682,6 +1729,10 @@ class App(QtCore.QObject):
         self.ui.excellon_options_form.excellon_opt_group.excellon_defaults_button.clicked.connect(
         self.ui.excellon_options_form.excellon_opt_group.excellon_defaults_button.clicked.connect(
             self.on_excellon_options_button)
             self.on_excellon_options_button)
 
 
+        # when there are arguments at application startup this get launched
+        self.args_at_startup.connect(self.on_startup_args)
+        self.log.debug("Finished connecting Signals.")
+
         # this is a flag to signal to other tools that the ui tooltab is locked and not accessible
         # this is a flag to signal to other tools that the ui tooltab is locked and not accessible
         self.tool_tab_locked = False
         self.tool_tab_locked = False
 
 
@@ -1691,21 +1742,18 @@ class App(QtCore.QObject):
         else:
         else:
             self.ui.splitter.setSizes([0, 1])
             self.ui.splitter.setSizes([0, 1])
 
 
-        # ###################
-        # ### Other setups ##
-        # ###################
+        # ###########################################
+        # ################# Other setups ############
+        # ###########################################
+
         # Sets up FlatCAMObj, FCProcess and FCProcessContainer.
         # Sets up FlatCAMObj, FCProcess and FCProcessContainer.
         self.setup_obj_classes()
         self.setup_obj_classes()
         self.setup_recent_items()
         self.setup_recent_items()
         self.setup_component_editor()
         self.setup_component_editor()
 
 
-        # ############
-        # ### Shell ##
-        # ############
-
-        # #########################
-        # Auto-complete KEYWORDS ##
-        # #########################
+        # ###########################################
+        # #######Auto-complete KEYWORDS #############
+        # ###########################################
         self.tcl_commands_list = ['add_circle', 'add_poly', 'add_polygon', 'add_polyline', 'add_rectangle',
         self.tcl_commands_list = ['add_circle', 'add_poly', 'add_polygon', 'add_polyline', 'add_rectangle',
                                   'aligndrill', 'clear',
                                   'aligndrill', 'clear',
                                   'aligndrillgrid', 'cncjob', 'cutout', 'delete', 'drillcncjob',
                                   'aligndrillgrid', 'cncjob', 'cutout', 'delete', 'drillcncjob',
@@ -1915,6 +1963,10 @@ class App(QtCore.QObject):
 
 
         self.myKeywords = self.tcl_commands_list + self.ordinary_keywords + self.tcl_keywords
         self.myKeywords = self.tcl_commands_list + self.ordinary_keywords + self.tcl_keywords
 
 
+        # ###########################################
+        # ########### Shell SETUP ###################
+        # ###########################################
+
         self.shell = FCShell(self, version=self.version)
         self.shell = FCShell(self, version=self.version)
         self.shell._edit.set_model_data(self.myKeywords)
         self.shell._edit.set_model_data(self.myKeywords)
         self.ui.code_editor.set_model_data(self.myKeywords)
         self.ui.code_editor.set_model_data(self.myKeywords)
@@ -1941,9 +1993,9 @@ class App(QtCore.QObject):
         else:
         else:
             self.ui.shell_dock.hide()
             self.ui.shell_dock.hide()
 
 
-        # ########################
-        # ### Tools and Plugins ##
-        # ########################
+        # ###########################################
+        # ######### Tools and Plugins ###############
+        # ###########################################
 
 
         self.dblsidedtool = None
         self.dblsidedtool = None
         self.measurement_tool = None
         self.measurement_tool = None
@@ -1969,6 +2021,10 @@ class App(QtCore.QObject):
         # self.f_parse = ParseFont(self)
         # self.f_parse = ParseFont(self)
         # self.parse_system_fonts()
         # self.parse_system_fonts()
 
 
+        # ###############################################
+        # ######## START-UP ARGUMENTS ###################
+        # ###############################################
+
         # test if the program was started with a script as parameter
         # test if the program was started with a script as parameter
         if self.cmd_line_shellfile:
         if self.cmd_line_shellfile:
             try:
             try:
@@ -1979,9 +2035,9 @@ class App(QtCore.QObject):
                 print("ERROR: ", ext)
                 print("ERROR: ", ext)
                 sys.exit(2)
                 sys.exit(2)
 
 
-        # ##########################
-        # ### Check for updates ####
-        # ##########################
+        # ###############################################
+        # ############# Check for updates ###############
+        # ###############################################
 
 
         # Separate thread (Not worker)
         # Separate thread (Not worker)
         # Check for updates on startup but only if the user consent and the app is not in Beta version
         # Check for updates on startup but only if the user consent and the app is not in Beta version
@@ -1994,9 +2050,9 @@ class App(QtCore.QObject):
                                    'params': []})
                                    'params': []})
             self.thr2.start(QtCore.QThread.LowPriority)
             self.thr2.start(QtCore.QThread.LowPriority)
 
 
-        # ###################################
-        # ### Variables for global usage ####
-        # ###################################
+        # ################################################
+        # ######### Variables for global usage ###########
+        # ################################################
 
 
         # coordinates for relative position display
         # coordinates for relative position display
         self.rel_point1 = (0, 0)
         self.rel_point1 = (0, 0)
@@ -2061,13 +2117,13 @@ class App(QtCore.QObject):
         self.dxf_list = ['dxf']
         self.dxf_list = ['dxf']
         self.pdf_list = ['pdf']
         self.pdf_list = ['pdf']
         self.prj_list = ['flatprj']
         self.prj_list = ['flatprj']
+        self.conf_list = ['flatconfig']
 
 
         # global variable used by NCC Tool to signal that some polygons could not be cleared, if True
         # global variable used by NCC Tool to signal that some polygons could not be cleared, if True
         # flag for polygons not cleared
         # flag for polygons not cleared
         self.poly_not_cleared = False
         self.poly_not_cleared = False
 
 
         # VisPy visuals
         # VisPy visuals
-        self.hover_shapes = ShapeCollection(parent=self.plotcanvas.vispy_canvas.view.scene, layers=1)
         self.isHovering = False
         self.isHovering = False
         self.notHovering = True
         self.notHovering = True
 
 
@@ -2096,7 +2152,7 @@ class App(QtCore.QObject):
         os.chmod(filename_factory, S_IREAD | S_IRGRP | S_IROTH)
         os.chmod(filename_factory, S_IREAD | S_IRGRP | S_IROTH)
 
 
         ####################################################
         ####################################################
-        # ### EDITOR section ###############################
+        # ### ADDING FlatCAM EDITORS section ###############
         ####################################################
         ####################################################
 
 
         # watch out for the position of the editors instantiation ... if it is done before a save of the default values
         # watch out for the position of the editors instantiation ... if it is done before a save of the default values
@@ -2113,8 +2169,33 @@ class App(QtCore.QObject):
 
 
         App.log.debug("END of constructor. Releasing control.")
         App.log.debug("END of constructor. Releasing control.")
 
 
-        # accept a project file as command line parameter
+        self.set_ui_title(name=_("New Project - Not saved"))
+
+        # accept some type file as command line parameter: FlatCAM project, FlatCAM preferences or scripts
         # the path/file_name must be enclosed in quotes if it contain spaces
         # the path/file_name must be enclosed in quotes if it contain spaces
+        if App.args:
+            self.args_at_startup.emit()
+
+
+    @staticmethod
+    def copy_and_overwrite(from_path, to_path):
+        """
+        From here:
+        https://stackoverflow.com/questions/12683834/how-to-copy-directory-recursively-in-python-and-overwrite-all
+        :param from_path: source path
+        :param to_path: destination path
+        :return: None
+        """
+        if os.path.exists(to_path):
+            shutil.rmtree(to_path)
+        try:
+            shutil.copytree(from_path, to_path)
+        except FileNotFoundError:
+            from_new_path = os.path.dirname(os.path.realpath(__file__)) + '\\flatcamGUI\\VisPyData\\data'
+            shutil.copytree(from_new_path, to_path)
+
+    def on_startup_args(self):
+        log.debug("Application was started with an argument. Processing ...")
         for argument in App.args:
         for argument in App.args:
             if '.FlatPrj' in argument:
             if '.FlatPrj' in argument:
                 try:
                 try:
@@ -2125,12 +2206,13 @@ class App(QtCore.QObject):
                     else:
                     else:
                         # self.open_project(project_name)
                         # self.open_project(project_name)
                         run_from_arg = True
                         run_from_arg = True
-                        self.worker_task.emit({'fcn': self.open_project,
-                                               'params': [project_name, run_from_arg]})
+                        # self.worker_task.emit({'fcn': self.open_project,
+                        #                        'params': [project_name, run_from_arg]})
+                        self.open_project(filename=project_name, run_from_arg=run_from_arg)
                 except Exception as e:
                 except Exception as e:
                     log.debug("Could not open FlatCAM project file as App parameter due: %s" % str(e))
                     log.debug("Could not open FlatCAM project file as App parameter due: %s" % str(e))
 
 
-            if '.FlatConfig' in argument:
+            elif '.FlatConfig' in argument:
                 try:
                 try:
                     file_name = str(argument)
                     file_name = str(argument)
 
 
@@ -2144,7 +2226,7 @@ class App(QtCore.QObject):
                 except Exception as e:
                 except Exception as e:
                     log.debug("Could not open FlatCAM Config file as App parameter due: %s" % str(e))
                     log.debug("Could not open FlatCAM Config file as App parameter due: %s" % str(e))
 
 
-            if '.FlatScript' in argument:
+            elif '.FlatScript' in argument:
                 try:
                 try:
                     file_name = str(argument)
                     file_name = str(argument)
 
 
@@ -2158,23 +2240,6 @@ class App(QtCore.QObject):
                 except Exception as e:
                 except Exception as e:
                     log.debug("Could not open FlatCAM Script file as App parameter due: %s" % str(e))
                     log.debug("Could not open FlatCAM Script file as App parameter due: %s" % str(e))
 
 
-    @staticmethod
-    def copy_and_overwrite(from_path, to_path):
-        """
-        From here:
-        https://stackoverflow.com/questions/12683834/how-to-copy-directory-recursively-in-python-and-overwrite-all
-        :param from_path: source path
-        :param to_path: destination path
-        :return: None
-        """
-        if os.path.exists(to_path):
-            shutil.rmtree(to_path)
-        try:
-            shutil.copytree(from_path, to_path)
-        except FileNotFoundError:
-            from_new_path = os.path.dirname(os.path.realpath(__file__)) + '\\flatcamGUI\\VisPyData\\data'
-            shutil.copytree(from_new_path, to_path)
-
     def set_ui_title(self, name):
     def set_ui_title(self, name):
         self.ui.setWindowTitle('FlatCAM %s %s - %s    %s' %
         self.ui.setWindowTitle('FlatCAM %s %s - %s    %s' %
                                (self.version,
                                (self.version,
@@ -3006,7 +3071,7 @@ class App(QtCore.QObject):
             # Save update options
             # Save update options
             try:
             try:
                 f = open(filename, "w")
                 f = open(filename, "w")
-                json.dump(defaults_from_file, f)
+                json.dump(defaults_from_file, f, default=to_dict, indent=2, sort_keys=True)
                 f.close()
                 f.close()
             except:
             except:
                 self.inform.emit(_("[ERROR_NOTCL] Failed to write defaults to file."))
                 self.inform.emit(_("[ERROR_NOTCL] Failed to write defaults to file."))
@@ -3106,6 +3171,11 @@ class App(QtCore.QObject):
         :param initialize: Function to run after creation of the object but before it is attached to the application.
         :param initialize: Function to run after creation of the object but before it is attached to the application.
         The function is called with 2 parameters: the new object and the App instance.
         The function is called with 2 parameters: the new object and the App instance.
         :type initialize: function
         :type initialize: function
+        :param active:
+        :param fit:
+        :param plot: If to plot the resulting object
+        :param autoselected: if the resulting object is autoselected in the Project tab and therefore in the
+        self.colleaction
         :return: None
         :return: None
         :rtype: None
         :rtype: None
         """
         """
@@ -3189,11 +3259,9 @@ class App(QtCore.QObject):
             obj.options['ymin'] = ymin
             obj.options['ymin'] = ymin
             obj.options['xmax'] = xmax
             obj.options['xmax'] = xmax
             obj.options['ymax'] = ymax
             obj.options['ymax'] = ymax
-        except:
-            log.warning("The object has no bounds properties.")
-            # don't plot objects with no bounds, there is nothing to plot
-            self.plot = False
-            pass
+        except Exception as e:
+            log.warning("The object has no bounds properties. %s" % str(e))
+            return "fail"
 
 
         FlatCAMApp.App.log.debug("Moving new object back to main thread.")
         FlatCAMApp.App.log.debug("Moving new object back to main thread.")
 
 
@@ -3438,7 +3506,7 @@ class App(QtCore.QObject):
     #     log.debug("Application defaults saved ... Exit event.")
     #     log.debug("Application defaults saved ... Exit event.")
     #     QtWidgets.qApp.quit()
     #     QtWidgets.qApp.quit()
 
 
-    def save_defaults(self, silent=False):
+    def save_defaults(self, silent=False, data_path=None):
         """
         """
         Saves application default options
         Saves application default options
         ``self.defaults`` to current_defaults.FlatConfig.
         ``self.defaults`` to current_defaults.FlatConfig.
@@ -3447,9 +3515,12 @@ class App(QtCore.QObject):
         """
         """
         self.report_usage("save_defaults")
         self.report_usage("save_defaults")
 
 
+        if data_path is None:
+            data_path = self.data_path
+
         # Read options from file
         # Read options from file
         try:
         try:
-            f = open(self.data_path + "/current_defaults.FlatConfig")
+            f = open(data_path + "/current_defaults.FlatConfig")
             defaults_file_content = f.read()
             defaults_file_content = f.read()
             f.close()
             f.close()
         except:
         except:
@@ -3506,7 +3577,7 @@ class App(QtCore.QObject):
 
 
         # Save update options
         # Save update options
         try:
         try:
-            f = open(self.data_path + "/current_defaults.FlatConfig", "w")
+            f = open(data_path + "/current_defaults.FlatConfig", "w")
             json.dump(defaults, f, default=to_dict, indent=2, sort_keys=True)
             json.dump(defaults, f, default=to_dict, indent=2, sort_keys=True)
             f.close()
             f.close()
         except:
         except:
@@ -3516,7 +3587,7 @@ class App(QtCore.QObject):
         if not silent:
         if not silent:
             self.inform.emit(_("[success] Defaults saved."))
             self.inform.emit(_("[success] Defaults saved."))
 
 
-    def save_factory_defaults(self, silent=False):
+    def save_factory_defaults(self, silent=False, data_path=None):
         """
         """
                 Saves application factory default options
                 Saves application factory default options
                 ``self.defaults`` to factory_defaults.FlatConfig.
                 ``self.defaults`` to factory_defaults.FlatConfig.
@@ -3526,9 +3597,12 @@ class App(QtCore.QObject):
                 """
                 """
         self.report_usage("save_factory_defaults")
         self.report_usage("save_factory_defaults")
 
 
+        if data_path is None:
+            data_path = self.data_path
+
         # Read options from file
         # Read options from file
         try:
         try:
-            f_f_def = open(self.data_path + "/factory_defaults.FlatConfig")
+            f_f_def = open(data_path + "/factory_defaults.FlatConfig")
             factory_defaults_file_content = f_f_def.read()
             factory_defaults_file_content = f_f_def.read()
             f_f_def.close()
             f_f_def.close()
         except:
         except:
@@ -3554,7 +3628,7 @@ class App(QtCore.QObject):
 
 
         # Save update options
         # Save update options
         try:
         try:
-            f_f_def_s = open(self.data_path + "/factory_defaults.FlatConfig", "w")
+            f_f_def_s = open(data_path + "/factory_defaults.FlatConfig", "w")
             json.dump(factory_defaults, f_f_def_s, default=to_dict, indent=2, sort_keys=True)
             json.dump(factory_defaults, f_f_def_s, default=to_dict, indent=2, sort_keys=True)
             f_f_def_s.close()
             f_f_def_s.close()
         except:
         except:
@@ -3608,11 +3682,96 @@ class App(QtCore.QObject):
         settings.setValue('axis_font_size',
         settings.setValue('axis_font_size',
                           self.ui.general_defaults_form.general_gui_set_group.axis_font_size_spinner.get_value())
                           self.ui.general_defaults_form.general_gui_set_group.axis_font_size_spinner.get_value())
 
 
+        settings.setValue('toolbar_lock', self.ui.lock_action.isChecked())
+
         # This will write the setting to the platform specific storage.
         # This will write the setting to the platform specific storage.
         del settings
         del settings
         log.debug("App.final_save() --> App UI state saved.")
         log.debug("App.final_save() --> App UI state saved.")
         QtWidgets.qApp.quit()
         QtWidgets.qApp.quit()
 
 
+    def on_portable_checked(self, state):
+        line_no = 0
+        data = None
+
+        if sys.platform != 'win32':
+            # this won't work in Linux or MacOS
+            return
+
+        # test if the app was frozen and choose the path for the configuration file
+        if getattr(sys, "frozen", False) is True:
+            current_data_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + '\\config'
+        else:
+            current_data_path = os.path.dirname(os.path.realpath(__file__)) + '\\config'
+
+        config_file = current_data_path  + '\\configuration.txt'
+        try:
+            with open(config_file, 'r') as f:
+                try:
+                    data = f.readlines()
+                except Exception as e:
+                    log.debug('App.__init__() -->%s' % str(e))
+                    return
+        except FileNotFoundError:
+            pass
+
+        for line in data:
+            line = line.strip('\n')
+            param = str(line).rpartition('=')
+            if param[0] == 'portable':
+                break
+            line_no += 1
+
+        if state:
+            data[line_no] = 'portable=True\n'
+            # create the new defauults files
+            # create current_defaults.FlatConfig file if there is none
+            try:
+                f = open(current_data_path + '/current_defaults.FlatConfig')
+                f.close()
+            except IOError:
+                App.log.debug('Creating empty current_defaults.FlatConfig')
+                f = open(current_data_path + '/current_defaults.FlatConfig', 'w')
+                json.dump({}, f)
+                f.close()
+
+            # create factory_defaults.FlatConfig file if there is none
+            try:
+                f = open(current_data_path + '/factory_defaults.FlatConfig')
+                f.close()
+            except IOError:
+                App.log.debug('Creating empty factory_defaults.FlatConfig')
+                f = open(current_data_path + '/factory_defaults.FlatConfig', 'w')
+                json.dump({}, f)
+                f.close()
+
+            try:
+                f = open(current_data_path + '/recent.json')
+                f.close()
+            except IOError:
+                App.log.debug('Creating empty recent.json')
+                f = open(current_data_path + '/recent.json', 'w')
+                json.dump([], f)
+                f.close()
+
+            try:
+                fp = open(current_data_path + '/recent_projects.json')
+                fp.close()
+            except IOError:
+                App.log.debug('Creating empty recent_projects.json')
+                fp = open(current_data_path + '/recent_projects.json', 'w')
+                json.dump([], fp)
+                fp.close()
+
+            # save the current defaults to the new defaults file
+            self.save_defaults(silent=True, data_path=current_data_path)
+            self.save_factory_defaults(silent=True, data_path=current_data_path)
+
+        else:
+            data[line_no] = 'portable=False\n'
+
+        with open(config_file, 'w') as f:
+            f.writelines(data)
+
     def on_toggle_shell(self):
     def on_toggle_shell(self):
         """
         """
         toggle shell if is  visible close it if  closed open it
         toggle shell if is  visible close it if  closed open it
@@ -4010,7 +4169,7 @@ class App(QtCore.QObject):
         msgbox = QtWidgets.QMessageBox()
         msgbox = QtWidgets.QMessageBox()
         msgbox.setWindowTitle(_("Toggle Units"))
         msgbox.setWindowTitle(_("Toggle Units"))
         msgbox.setWindowIcon(QtGui.QIcon('share/toggle_units32.png'))
         msgbox.setWindowIcon(QtGui.QIcon('share/toggle_units32.png'))
-        msgbox.setText(_("<B>Change project units ...</B>"))
+        msgbox.setText("<B>%s</B>" % _("Change project units ..."))
         msgbox.setInformativeText(_("Changing the units of the project causes all geometrical "
         msgbox.setInformativeText(_("Changing the units of the project causes all geometrical "
                                     "properties of all objects to be scaled accordingly.\nContinue?"))
                                     "properties of all objects to be scaled accordingly.\nContinue?"))
         bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
         bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
@@ -4545,6 +4704,13 @@ class App(QtCore.QObject):
         self.ui.cncjob_defaults_form.cncjob_gen_group.annotation_fontcolor_entry.set_value(new_val_sel)
         self.ui.cncjob_defaults_form.cncjob_gen_group.annotation_fontcolor_entry.set_value(new_val_sel)
         self.defaults['global_proj_item_dis_color'] = new_val_sel
         self.defaults['global_proj_item_dis_color'] = new_val_sel
 
 
+    def on_notebook_tab_rmb_click(self, checked):
+        self.ui.notebook.set_detachable(val=checked)
+        self.defaults["global_tabs_detachable"] = checked
+
+        self.ui.plot_tab_area.set_detachable(val=checked)
+        self.defaults["global_tabs_detachable"] = checked
+
     def on_deselect_all(self):
     def on_deselect_all(self):
         self.collection.set_all_inactive()
         self.collection.set_all_inactive()
         self.delete_selection_shape()
         self.delete_selection_shape()
@@ -4990,7 +5156,7 @@ class App(QtCore.QObject):
                 msgbox = QtWidgets.QMessageBox()
                 msgbox = QtWidgets.QMessageBox()
                 msgbox.setWindowTitle(_("Delete objects"))
                 msgbox.setWindowTitle(_("Delete objects"))
                 msgbox.setWindowIcon(QtGui.QIcon('share/deleteshape32.png'))
                 msgbox.setWindowIcon(QtGui.QIcon('share/deleteshape32.png'))
-                # msgbox.setText(_("<B>Delete FlatCAM objects ...</B>"))
+                # msgbox.setText("<B>%s</B>" % _("Change project units ..."))
                 msgbox.setText(_("Are you sure you want to permanently delete\n"
                 msgbox.setText(_("Are you sure you want to permanently delete\n"
                                  "the selected objects?"))
                                  "the selected objects?"))
                 bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
                 bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
@@ -7166,12 +7332,15 @@ class App(QtCore.QObject):
 
 
         self.should_we_save = False
         self.should_we_save = False
 
 
-    def on_file_saveprojectas(self, make_copy=False, thread=True, quit=False):
+    def on_file_saveprojectas(self, make_copy=False, use_thread=True, quit_action=False):
         """
         """
         Callback for menu item File->Save Project As... Opens a file
         Callback for menu item File->Save Project As... Opens a file
         chooser and saves the project to the given file via
         chooser and saves the project to the given file via
         ``self.save_project()``.
         ``self.save_project()``.
 
 
+        :param make_copy if to be create a copy of the project; boolean
+        :param use_thread: if to be run in a separate thread; boolean
+        :param quit_action: if to be followed by quiting the application; boolean
         :return: None
         :return: None
         """
         """
 
 
@@ -7202,16 +7371,17 @@ class App(QtCore.QObject):
         except IOError:
         except IOError:
             pass
             pass
 
 
-        if thread is True:
+        if use_thread is True:
             self.worker_task.emit({'fcn': self.save_project,
             self.worker_task.emit({'fcn': self.save_project,
-                                   'params': [filename, quit]})
+                                   'params': [filename, quit_action]})
         else:
         else:
-            self.save_project(filename, quit)
+            self.save_project(filename, quit_action)
 
 
         # self.save_project(filename)
         # self.save_project(filename)
         if self.defaults["global_open_style"] is False:
         if self.defaults["global_open_style"] is False:
             self.file_opened.emit("project", filename)
             self.file_opened.emit("project", filename)
         self.file_saved.emit("project", filename)
         self.file_saved.emit("project", filename)
+
         if not make_copy:
         if not make_copy:
             self.project_filename = filename
             self.project_filename = filename
 
 
@@ -7222,7 +7392,9 @@ class App(QtCore.QObject):
         """
         """
         Exports a Geometry Object to an SVG file.
         Exports a Geometry Object to an SVG file.
 
 
+        :param obj_name: the name of the FlatCAM object to be saved as SVG
         :param filename: Path to the SVG file to save to.
         :param filename: Path to the SVG file to save to.
+        :param scale_factor: factor by which to change/scale the thickness of the features
         :return:
         :return:
         """
         """
         self.report_usage("export_svg()")
         self.report_usage("export_svg()")
@@ -7284,9 +7456,12 @@ class App(QtCore.QObject):
         """
         """
         Exports a Geometry Object to an SVG file in negative.
         Exports a Geometry Object to an SVG file in negative.
 
 
+        :param obj_name: the name of the FlatCAM object to be saved as SVG
+        :param box_name: the name of the FlatCAM object to be used as delimitation of the content to be saved
         :param filename: Path to the SVG file to save to.
         :param filename: Path to the SVG file to save to.
-        :param: use_thread: If True use threads
-        :type: Bool
+        :param boundary: thickness of a black border to surround all the features
+        :param scale_factor: factor by which to change/scale the thickness of the features
+        :param use_thread: if to be run in a separate thread; boolean
         :return:
         :return:
         """
         """
         self.report_usage("export_negative()")
         self.report_usage("export_negative()")
@@ -7408,11 +7583,13 @@ class App(QtCore.QObject):
 
 
     def export_svg_black(self, obj_name, box_name, filename, scale_factor=0.00, use_thread=True):
     def export_svg_black(self, obj_name, box_name, filename, scale_factor=0.00, use_thread=True):
         """
         """
-        Exports a Geometry Object to an SVG file in negative.
+        Exports a Geometry Object to an SVG file in positive black.
 
 
+        :param obj_name: the name of the FlatCAM object to be saved as SVG
+        :param box_name: the name of the FlatCAM object to be used as delimitation of the content to be saved
         :param filename: Path to the SVG file to save to.
         :param filename: Path to the SVG file to save to.
-        :param: use_thread: If True use threads
-        :type: Bool
+        :param scale_factor: factor by which to change/scale the thickness of the features
+        :param use_thread: if to be run in a separate thread; boolean
         :return:
         :return:
         """
         """
         self.report_usage("export_svg_black()")
         self.report_usage("export_svg_black()")
@@ -7529,9 +7706,11 @@ class App(QtCore.QObject):
 
 
     def save_source_file(self, obj_name, filename, use_thread=True):
     def save_source_file(self, obj_name, filename, use_thread=True):
         """
         """
-        Exports a Gerber Object to an Gerber file.
+        Exports a FlatCAM Object to an Gerber/Excellon file.
 
 
+        :param obj_name: the name of the FlatCAM object for which to save it's embedded source file
         :param filename: Path to the Gerber file to save to.
         :param filename: Path to the Gerber file to save to.
+        :param use_thread: if to be run in a separate thread
         :return:
         :return:
         """
         """
         self.report_usage("save source file()")
         self.report_usage("save source file()")
@@ -7565,7 +7744,9 @@ class App(QtCore.QObject):
         """
         """
         Exports a Excellon Object to an Excellon file.
         Exports a Excellon Object to an Excellon file.
 
 
+        :param obj_name: the name of the FlatCAM object to be saved as Excellon
         :param filename: Path to the Excellon file to save to.
         :param filename: Path to the Excellon file to save to.
+        :param use_thread: if to be run in a separate thread
         :return:
         :return:
         """
         """
         self.report_usage("export_excellon()")
         self.report_usage("export_excellon()")
@@ -7701,7 +7882,9 @@ class App(QtCore.QObject):
         """
         """
         Exports a Gerber Object to an Gerber file.
         Exports a Gerber Object to an Gerber file.
 
 
+        :param obj_name: the name of the FlatCAM object to be saved as Gerber
         :param filename: Path to the Gerber file to save to.
         :param filename: Path to the Gerber file to save to.
+        :param use_thread: if to be run in a separate thread
         :return:
         :return:
         """
         """
         self.report_usage("export_gerber()")
         self.report_usage("export_gerber()")
@@ -7824,7 +8007,9 @@ class App(QtCore.QObject):
         """
         """
         Exports a Geometry Object to an DXF file.
         Exports a Geometry Object to an DXF file.
 
 
+        :param obj_name: the name of the FlatCAM object to be saved as DXF
         :param filename: Path to the DXF file to save to.
         :param filename: Path to the DXF file to save to.
+        :param use_thread: if to be run in a separate thread
         :return:
         :return:
         """
         """
         self.report_usage("export_dxf()")
         self.report_usage("export_dxf()")
@@ -7868,7 +8053,7 @@ class App(QtCore.QObject):
                 def job_thread_exc(app_obj):
                 def job_thread_exc(app_obj):
                     ret = make_dxf()
                     ret = make_dxf()
                     if ret == 'fail':
                     if ret == 'fail':
-                        self.inform.emit(_('[[WARNING_NOTCL]] Could not export DXF file.'))
+                        app_obj.inform.emit(_('[[WARNING_NOTCL]] Could not export DXF file.'))
                         return
                         return
 
 
                 self.worker_task.emit({'fcn': job_thread_exc, 'params': [self]})
                 self.worker_task.emit({'fcn': job_thread_exc, 'params': [self]})
@@ -8254,6 +8439,8 @@ class App(QtCore.QObject):
         """
         """
         App.log.debug("Opening project: " + filename)
         App.log.debug("Opening project: " + filename)
 
 
+        self.set_ui_title(name=_("Loading Project ... Please Wait ..."))
+
         # Open and parse an uncompressed Project file
         # Open and parse an uncompressed Project file
         try:
         try:
             f = open(filename, 'r')
             f = open(filename, 'r')
@@ -8293,20 +8480,26 @@ class App(QtCore.QObject):
         self.set_screen_units(self.options["units"])
         self.set_screen_units(self.options["units"])
 
 
         # Re create objects
         # Re create objects
-        App.log.debug("Re-creating objects...")
+        App.log.debug(" **************** Started PROEJCT loading... **************** ")
+
         for obj in d['objs']:
         for obj in d['objs']:
             def obj_init(obj_inst, app_inst):
             def obj_init(obj_inst, app_inst):
                 obj_inst.from_dict(obj)
                 obj_inst.from_dict(obj)
-            App.log.debug(obj['kind'] + ":  " + obj['options']['name'])
+            App.log.debug("Recreating from opened project an %s object: %s" %
+                          (obj['kind'].capitalize(), obj['options']['name']))
+
+            self.set_ui_title(name="{} {}: {}".format(_("Loading Project ... restoring"), obj['kind'].upper(), obj['options']['name']))
+
             self.new_object(obj['kind'], obj['options']['name'], obj_init, active=False, fit=False, plot=True)
             self.new_object(obj['kind'], obj['options']['name'], obj_init, active=False, fit=False, plot=True)
-        self.plot_all()
+
+        # self.plot_all()
         self.inform.emit(_("[success] Project loaded from: %s") % filename)
         self.inform.emit(_("[success] Project loaded from: %s") % filename)
 
 
         self.should_we_save = False
         self.should_we_save = False
         self.file_opened.emit("project", filename)
         self.file_opened.emit("project", filename)
         self.set_ui_title(name=self.project_filename)
         self.set_ui_title(name=self.project_filename)
 
 
-        App.log.debug("Project loaded")
+        App.log.debug(" **************** Finished PROJECT loading... **************** ")
 
 
     def propagate_defaults(self, silent=False):
     def propagate_defaults(self, silent=False):
         """
         """
@@ -8856,6 +9049,35 @@ The normal flow when working in FlatCAM is the following:</span></p>
             _("info")
             _("info")
         )
         )
 
 
+    def on_plotcanvas_setup(self, container=None):
+        """
+        This is doing the setup for the plot area (VisPy canvas)
+
+        :param container: widget where to install the canvas
+        :return: None
+        """
+        if container:
+            plot_container = container
+        else:
+            plot_container = self.ui.right_layout
+
+        self.plotcanvas = PlotCanvas(plot_container, self)
+
+        # So it can receive key presses
+        self.plotcanvas.vispy_canvas.native.setFocus()
+
+        self.plotcanvas.vis_connect('mouse_move', self.on_mouse_move_over_plot)
+        self.plotcanvas.vis_connect('mouse_press', self.on_mouse_click_over_plot)
+        self.plotcanvas.vis_connect('mouse_release', self.on_mouse_click_release_over_plot)
+        self.plotcanvas.vis_connect('mouse_double_click', self.on_double_click_over_plot)
+
+        # Keys over plot enabled
+        self.plotcanvas.vis_connect('key_press', self.ui.keyPressEvent)
+
+        self.app_cursor = self.plotcanvas.new_cursor()
+        self.app_cursor.enabled = False
+        self.hover_shapes = ShapeCollection(parent=self.plotcanvas.vispy_canvas.view.scene, layers=1)
+
     def on_zoom_fit(self, event):
     def on_zoom_fit(self, event):
         """
         """
         Callback for zoom-out request. This can be either from the corresponding
         Callback for zoom-out request. This can be either from the corresponding
@@ -8868,6 +9090,12 @@ The normal flow when working in FlatCAM is the following:</span></p>
 
 
         self.plotcanvas.fit_view()
         self.plotcanvas.fit_view()
 
 
+    def on_zoom_in(self):
+        self.plotcanvas.zoom(1 / float(self.defaults['global_zoom_ratio']))
+
+    def on_zoom_out(self):
+        self.plotcanvas.zoom(float(self.defaults['global_zoom_ratio']))
+
     def disable_all_plots(self):
     def disable_all_plots(self):
         self.report_usage("disable_all_plots()")
         self.report_usage("disable_all_plots()")
 
 
@@ -8906,7 +9134,6 @@ The normal flow when working in FlatCAM is the following:</span></p>
         :param objects: list of Objects to be enabled
         :param objects: list of Objects to be enabled
         :return:
         :return:
         """
         """
-
         log.debug("Enabling plots ...")
         log.debug("Enabling plots ...")
         self.inform.emit(_("Working ..."))
         self.inform.emit(_("Working ..."))
         for obj in objects:
         for obj in objects:
@@ -8970,12 +9197,13 @@ The normal flow when working in FlatCAM is the following:</span></p>
         for obj in objects:
         for obj in objects:
             obj.on_generatecnc_button_click()
             obj.on_generatecnc_button_click()
 
 
-    def save_project(self, filename, quit=False):
+    def save_project(self, filename, quit_action=False):
         """
         """
         Saves the current project to the specified file.
         Saves the current project to the specified file.
 
 
         :param filename: Name of the file in which to save.
         :param filename: Name of the file in which to save.
         :type filename: str
         :type filename: str
+        :param quit_action: if the project saving will be followed by an app quit; boolean
         :return: None
         :return: None
         """
         """
         self.log.debug("save_project()")
         self.log.debug("save_project()")
@@ -9037,28 +9265,37 @@ The normal flow when working in FlatCAM is the following:</span></p>
                 else:
                 else:
                     self.inform.emit(_("[ERROR_NOTCL] Failed to save project file: %s. Retry to save it.") % filename)
                     self.inform.emit(_("[ERROR_NOTCL] Failed to save project file: %s. Retry to save it.") % filename)
 
 
+                settings = QSettings("Open Source", "FlatCAM")
+                lock_state = self.ui.lock_action.isChecked()
+                settings.setValue('toolbar_lock', lock_state)
+
+                # This will write the setting to the platform specific storage.
+                del settings
+
             # if quit:
             # if quit:
                 # t = threading.Thread(target=lambda: self.check_project_file_size(1, filename=filename))
                 # t = threading.Thread(target=lambda: self.check_project_file_size(1, filename=filename))
                 # t.start()
                 # t.start()
-            self.start_delayed_quit(delay=500, filename=filename, quit=quit)
+            self.start_delayed_quit(delay=500, filename=filename, should_quit=quit_action)
 
 
-    def start_delayed_quit(self, delay, filename, quit=None):
+    def start_delayed_quit(self, delay, filename, should_quit=None):
         """
         """
 
 
         :param delay:       period of checking if project file size is more than zero; in seconds
         :param delay:       period of checking if project file size is more than zero; in seconds
         :param filename:    the name of the project file to be checked periodically for size more than zero
         :param filename:    the name of the project file to be checked periodically for size more than zero
+        :param should_quit: if the task finished will be followed by an app quit; boolean
         :return:
         :return:
         """
         """
-        to_quit = quit
+        to_quit = should_quit
         self.save_timer = QtCore.QTimer()
         self.save_timer = QtCore.QTimer()
         self.save_timer.setInterval(delay)
         self.save_timer.setInterval(delay)
-        self.save_timer.timeout.connect(lambda: self.check_project_file_size(filename=filename, quit=to_quit))
+        self.save_timer.timeout.connect(lambda: self.check_project_file_size(filename=filename, should_quit=to_quit))
         self.save_timer.start()
         self.save_timer.start()
 
 
-    def check_project_file_size(self, filename, quit=None):
+    def check_project_file_size(self, filename, should_quit=None):
         """
         """
 
 
         :param filename: the name of the project file to be checked periodically for size more than zero
         :param filename: the name of the project file to be checked periodically for size more than zero
+        :param should_quit: will quit the app if True; boolean
         :return:
         :return:
         """
         """
 
 
@@ -9066,9 +9303,9 @@ The normal flow when working in FlatCAM is the following:</span></p>
             if os.stat(filename).st_size > 0:
             if os.stat(filename).st_size > 0:
                 self.save_in_progress = False
                 self.save_in_progress = False
                 self.save_timer.stop()
                 self.save_timer.stop()
-                if quit:
+                if should_quit:
                     self.app_quit.emit()
                     self.app_quit.emit()
-        except Exception:
+        except Exception as e:
             traceback.print_exc()
             traceback.print_exc()
 
 
     def on_options_app2project(self):
     def on_options_app2project(self):

+ 17 - 12
FlatCAMObj.py

@@ -136,7 +136,6 @@ class FlatCAMObj(QtCore.QObject):
     def on_options_change(self, key):
     def on_options_change(self, key):
         # Update form on programmatically options change
         # Update form on programmatically options change
         self.set_form_item(key)
         self.set_form_item(key)
-
         # Set object visibility
         # Set object visibility
         if key == 'plot':
         if key == 'plot':
             self.visible = self.options['plot']
             self.visible = self.options['plot']
@@ -774,7 +773,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
 
 
         if self.ui.follow_cb.get_value() is True:
         if self.ui.follow_cb.get_value() is True:
             obj = self.app.collection.get_active()
             obj = self.app.collection.get_active()
-            obj.follow()
+            obj.follow_geo()
             # in the end toggle the visibility of the origin object so we can see the generated Geometry
             # in the end toggle the visibility of the origin object so we can see the generated Geometry
             obj.ui.plot_cb.toggle()
             obj.ui.plot_cb.toggle()
         else:
         else:
@@ -786,7 +785,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
 
 
         if self.ui.follow_cb.get_value() is True:
         if self.ui.follow_cb.get_value() is True:
             obj = self.app.collection.get_active()
             obj = self.app.collection.get_active()
-            obj.follow()
+            obj.follow_geo()
             # in the end toggle the visibility of the origin object so we can see the generated Geometry
             # in the end toggle the visibility of the origin object so we can see the generated Geometry
             obj.ui.plot_cb.toggle()
             obj.ui.plot_cb.toggle()
         else:
         else:
@@ -1130,6 +1129,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         :return: None
         :return: None
         :rtype: None
         :rtype: None
         """
         """
+        log.debug("FlatCAMObj.FlatCAMGerber.convert_units()")
 
 
         factor = Gerber.convert_units(self, units)
         factor = Gerber.convert_units(self, units)
 
 
@@ -2769,8 +2769,9 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
         self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
         self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
 
 
     def convert_units(self, units):
     def convert_units(self, units):
-        factor = Excellon.convert_units(self, units)
+        log.debug("FlatCAMObj.FlatCAMExcellon.convert_units()")
 
 
+        factor = Excellon.convert_units(self, units)
         self.options['drillz'] = float(self.options['drillz']) * factor
         self.options['drillz'] = float(self.options['drillz']) * factor
         self.options['travelz'] = float(self.options['travelz']) * factor
         self.options['travelz'] = float(self.options['travelz']) * factor
         self.options['feedrate'] = float(self.options['feedrate']) * factor
         self.options['feedrate'] = float(self.options['feedrate']) * factor
@@ -3423,7 +3424,6 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             self.ui.level.setText(_(
             self.ui.level.setText(_(
                 '<span style="color:red;"><b>Advanced</b></span>'
                 '<span style="color:red;"><b>Advanced</b></span>'
             ))
             ))
-
         self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
         self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
         self.ui.generate_cnc_button.clicked.connect(self.on_generatecnc_button_click)
         self.ui.generate_cnc_button.clicked.connect(self.on_generatecnc_button_click)
         self.ui.paint_tool_button.clicked.connect(lambda: self.app.paint_tool.run(toggle=False))
         self.ui.paint_tool_button.clicked.connect(lambda: self.app.paint_tool.run(toggle=False))
@@ -4958,6 +4958,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
         :return: None
         :return: None
         :rtype: None
         :rtype: None
         """
         """
+        log.debug("FlatCAMObj.FlatCAMGeometry.scale()")
 
 
         try:
         try:
             xfactor = float(xfactor)
             xfactor = float(xfactor)
@@ -5027,6 +5028,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
         :return: None
         :return: None
         :rtype: None
         :rtype: None
         """
         """
+        log.debug("FlatCAMObj.FlatCAMGeometry.offset()")
 
 
         try:
         try:
             dx, dy = vect
             dx, dy = vect
@@ -5057,6 +5059,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
         self.app.inform.emit(_("[success] Geometry Offset done."))
         self.app.inform.emit(_("[success] Geometry Offset done."))
 
 
     def convert_units(self, units):
     def convert_units(self, units):
+        log.debug("FlatCAMObj.FlatCAMGeometry.convert_units()")
+
         self.ui_disconnect()
         self.ui_disconnect()
 
 
         factor = Geometry.convert_units(self, units)
         factor = Geometry.convert_units(self, units)
@@ -5203,11 +5207,11 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                 for tooluid_key in self.tools:
                 for tooluid_key in self.tools:
                     solid_geometry = self.tools[tooluid_key]['solid_geometry']
                     solid_geometry = self.tools[tooluid_key]['solid_geometry']
                     self.plot_element(solid_geometry, visible=visible)
                     self.plot_element(solid_geometry, visible=visible)
-
-            # plot solid geometry that may be an direct attribute of the geometry object
-            # for SingleGeo
-            if self.solid_geometry:
-                self.plot_element(self.solid_geometry, visible=visible)
+            else:
+                # plot solid geometry that may be an direct attribute of the geometry object
+                # for SingleGeo
+                if self.solid_geometry:
+                    self.plot_element(self.solid_geometry, visible=visible)
 
 
             # self.plot_element(self.solid_geometry, visible=self.options['plot'])
             # self.plot_element(self.solid_geometry, visible=self.options['plot'])
             self.shapes.redraw()
             self.shapes.redraw()
@@ -5217,8 +5221,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
     def on_plot_cb_click(self, *args):
     def on_plot_cb_click(self, *args):
         if self.muted_ui:
         if self.muted_ui:
             return
             return
-        self.plot()
         self.read_form_item('plot')
         self.read_form_item('plot')
+        self.plot()
 
 
         self.ui_disconnect()
         self.ui_disconnect()
         cb_flag = self.ui.plot_cb.isChecked()
         cb_flag = self.ui.plot_cb.isChecked()
@@ -6024,8 +6028,9 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
         self.annotation.redraw()
         self.annotation.redraw()
 
 
     def convert_units(self, units):
     def convert_units(self, units):
+        log.debug("FlatCAMObj.FlatCAMECNCjob.convert_units()")
+
         factor = CNCjob.convert_units(self, units)
         factor = CNCjob.convert_units(self, units)
-        FlatCAMApp.App.log.debug("FlatCAMCNCjob.convert_units()")
         self.options["tooldia"] = float(self.options["tooldia"]) * factor
         self.options["tooldia"] = float(self.options["tooldia"]) * factor
 
 
         param_list = ['cutz', 'depthperpass', 'travelz', 'feedrate', 'feedrate_z', 'feedrate_rapid',
         param_list = ['cutz', 'depthperpass', 'travelz', 'feedrate', 'feedrate_z', 'feedrate_rapid',

+ 2 - 0
FlatCAMWorker.py

@@ -7,6 +7,7 @@
 # ########################################################## ##
 # ########################################################## ##
 
 
 from PyQt5 import QtCore
 from PyQt5 import QtCore
+# import traceback
 
 
 
 
 class Worker(QtCore.QObject):
 class Worker(QtCore.QObject):
@@ -60,6 +61,7 @@ class Worker(QtCore.QObject):
                 task['fcn'](*task['params'])
                 task['fcn'](*task['params'])
             except Exception as e:
             except Exception as e:
                 self.app.thread_exception.emit(e)
                 self.app.thread_exception.emit(e)
+                # print(traceback.format_exc())
                 # raise e
                 # raise e
             finally:
             finally:
                 self.task_completed.emit(self.name)
                 self.task_completed.emit(self.name)

+ 1 - 1
ObjectCollection.py

@@ -693,7 +693,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         :param name: Name of the FlatCAM Object
         :param name: Name of the FlatCAM Object
         :return: None
         :return: None
         """
         """
-        log.debug("ObjectCollection.set_inactive()")
+        # log.debug("ObjectCollection.set_inactive()")
 
 
         obj = self.get_by_name(name)
         obj = self.get_by_name(name)
         item = obj.item
         item = obj.item

+ 68 - 0
README.md

@@ -9,6 +9,74 @@ CAD program, and create G-Code for Isolation routing.
 
 
 =================================================
 =================================================
 
 
+23.08.2019
+
+- in Tool Cutout for the manual gaps, right mouse button click will exit from the action of adding gaps
+- in Tool Cutout tool I've added the possibility to create a cutout without bridge gaps; added the 'None' option in the Gaps combobox
+- in NCC Tool added ability to add multiple zones to clear when Area option is checked and the modifier key is pressed (either CTRL or SHIFT as set in Preferences). Right click of the mouse is an additional way to finish the job.
+- fixed a bug in Excellon Editor that made that the selection of drills is always cumulative
+- in Paint Tool added ability to add multiple zones to paint when Area option is checked and the modifier key is pressed (either CTRL or SHIFT as set in Preferences). Right click of the mouse is an additional way to finish the job.
+- in Paint Tool and NCC Tool, for the Area option, now mouse panning is allowed while adding areas to process
+- for all the FlatCAM tools launched from toolbar the behavior is modified: first click it will launch the tool; second click: if the Tool tab has focus it will close the tool but if another tab is selected, the tool will have focus
+- modified the NCC Tool and Paint Tool to work multiple times after first launch
+- fixed the issue with GUI entries content being deselected on right click in the box in order to copy the value
+- some changes in GUI tooltips
+- modified the way key modifiers are detected in Gerber Editor Selection class and in Excellon Editor Selection class
+- updated the translations
+- fixed aperture move in Gerber Editor
+- fixed drills/slots move in Excellon Editor
+- RELEASE 8.96
+
+22.08.2019
+
+- added ability to turn ON/OFF the detachable capability of the tabs in Notebook through a context menu activated by right mouse button click on the Notebook header
+- added ability to turn ON/OFF the detachable capability of the tabs in Plot Tab Area through a context menu activated by right mouse button click on the Notebook header
+- added possibility to turn application portable from the Edit -> Preferences -> General -> App. Preferences -> Portable checkbox
+- moved the canvas setup into it's own function and called it in the init() function
+- fixed the Buffer Tool in Geometry Editor; made the Buffer entry field a QDoubleSpinner and set the lower limit to zero.
+- fixed Tool Cutout so when the target Gerber is a single Polygon then the created manual geometry will follow the shape if shape is freeform
+- fixed TclCommandFollow command; an older function name was used who yielded wrong results
+- in Tool Cutout for the manual gaps, now the moving geometry that cuts gaps will orient itself to fit the angle of the cutout geometry
+
+21.08.2019
+
+- added feature in Paint Tool allowing the painting to be done on Gerber objects
+- added feature in Paint Tool to set how (and if) the tools are sorted
+- added Edit -> Preferences GUI entries for the above just added features
+- added new entry in Properties Tool which is the calculated Convex Hull Area (should give a more precise area for the irregular shapes than the box area)
+- added some more strings in Properties Tool for the translation
+- in NCC Tool added area selection feature
+- fixed bug in Excellon parser for the Excellon files that do not put the type of zero suppression they use in the file (like DipTrace eCAD)
+- fixed some issues introduced in NCC Tool
+
+20.08.2019
+
+- added ability to do copper clearing through NCC Tool on Geometry objects
+- replaced the layout from Grid to Form for the Reference objects comboboxes in Paint Tool and in NCC Tool
+
+19.08.2019
+
+- updated the Edit -> Preferences to include also the Gerber Editor complete Preferences
+- started to update the app strings to make it easier for future translations
+- fixed the POT file and the German translation
+- some mods in the Tool Sub
+- fixed bug in Tool Sub that created issues when toggling visibility of the plots
+- fixed the Spanish, Brazilian Portuguese and Romanian translations
+
+18.08.2019
+
+- made the exported preferences formatted therefore more easily read
+- projects at startup don't work in another thread so there is no multithreading if I want to double click an project and to load it
+- added messages in the application window title which show the progress in loading a project (which is not thread-safe therefore keeping the app from fully initialize until finished)
+- in NCC Tool added a new parameter (radio button) that offer the choice on the order of the tools both in tools table and in execution of engraving; added as a parameter also in Edit -> Preferences -> Tools -> NCC Tool
+- added possibility to drag & drop FlatCAM config files (*.FlatConfig) into the canvas to be opened into the application
+- added GUI in Paint tool in beginning to add Paint by external reference object 
+- finished adding in Paint Tool the usage of an external object to set the extent of th area painted. For simple shapes (single Polygon) the shape can be anything, for the rest will be a convex hull of the reference object
+- modified NCC tool so for simple objects (single Polygon) the external object used as reference can have any shape, for the other types of objects the copper cleared area will be the convex hull of the reference object
+- modified the strings of the app wherever they contained the char seq <b> </b> so it is not included in the translated string
+- updated the translation files for the modified strings (and for the newly added strings)
+- added ability to lock toolbars within the context menu that is popped up on any toolbars right mouse click. The value is saved in QSettings and it is persistent between application startup's.
+
 17.08.2019
 17.08.2019
 
 
 - added estimated time of routing for the CNCJob and added travelled distance parameter for geometry, too
 - added estimated time of routing for the CNCJob and added travelled distance parameter for geometry, too

+ 38 - 9
camlib.py

@@ -229,7 +229,8 @@ class Geometry(object):
         # fixed issue of getting bounds only for one level lists of objects
         # fixed issue of getting bounds only for one level lists of objects
         # now it can get bounds for nested lists of objects
         # now it can get bounds for nested lists of objects
 
 
-        log.debug("Geometry->bounds()")
+        log.debug("camlib.Geometry.bounds()")
+
         if self.solid_geometry is None:
         if self.solid_geometry is None:
             log.debug("solid_geometry is None")
             log.debug("solid_geometry is None")
             return 0, 0, 0, 0
             return 0, 0, 0, 0
@@ -1278,7 +1279,7 @@ class Geometry(object):
         :return: Scaling factor resulting from unit change.
         :return: Scaling factor resulting from unit change.
         :rtype: float
         :rtype: float
         """
         """
-        log.debug("Geometry.convert_units()")
+        log.debug("camlib.Geometry.convert_units()")
 
 
         if units.upper() == self.units.upper():
         if units.upper() == self.units.upper():
             return 1.0
             return 1.0
@@ -1378,6 +1379,7 @@ class Geometry(object):
         :type point: list
         :type point: list
         :return: None
         :return: None
         """
         """
+        log.debug("camlib.Geometry.mirror()")
 
 
         px, py = point
         px, py = point
         xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
         xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
@@ -1420,6 +1422,7 @@ class Geometry(object):
         See shapely manual for more information:
         See shapely manual for more information:
         http://toblerity.org/shapely/manual.html#affine-transformations
         http://toblerity.org/shapely/manual.html#affine-transformations
         """
         """
+        log.debug("camlib.Geometry.rotate()")
 
 
         px, py = point
         px, py = point
 
 
@@ -1460,6 +1463,8 @@ class Geometry(object):
         See shapely manual for more information:
         See shapely manual for more information:
         http://toblerity.org/shapely/manual.html#affine-transformations
         http://toblerity.org/shapely/manual.html#affine-transformations
         """
         """
+        log.debug("camlib.Geometry.skew()")
+
         px, py = point
         px, py = point
 
 
         def skew_geom(obj):
         def skew_geom(obj):
@@ -3298,7 +3303,8 @@ class Gerber (Geometry):
         # fixed issue of getting bounds only for one level lists of objects
         # fixed issue of getting bounds only for one level lists of objects
         # now it can get bounds for nested lists of objects
         # now it can get bounds for nested lists of objects
 
 
-        log.debug("Gerber->bounds()")
+        log.debug("camlib.Gerber.bounds()")
+
         if self.solid_geometry is None:
         if self.solid_geometry is None:
             log.debug("solid_geometry is None")
             log.debug("solid_geometry is None")
             return 0, 0, 0, 0
             return 0, 0, 0, 0
@@ -3442,6 +3448,8 @@ class Gerber (Geometry):
         :type vect: tuple
         :type vect: tuple
         :return: None
         :return: None
         """
         """
+        log.debug("camlib.Gerber.offset()")
+
         try:
         try:
             dx, dy = vect
             dx, dy = vect
         except TypeError:
         except TypeError:
@@ -3504,6 +3512,7 @@ class Gerber (Geometry):
         :type point: list
         :type point: list
         :return: None
         :return: None
         """
         """
+        log.debug("camlib.Gerber.mirror()")
 
 
         px, py = point
         px, py = point
         xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
         xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
@@ -3554,6 +3563,7 @@ class Gerber (Geometry):
         See shapely manual for more information:
         See shapely manual for more information:
         http://toblerity.org/shapely/manual.html#affine-transformations
         http://toblerity.org/shapely/manual.html#affine-transformations
         """
         """
+        log.debug("camlib.Gerber.skew()")
 
 
         px, py = point
         px, py = point
 
 
@@ -3596,6 +3606,7 @@ class Gerber (Geometry):
         :param point:
         :param point:
         :return:
         :return:
         """
         """
+        log.debug("camlib.Gerber.rotate()")
 
 
         px, py = point
         px, py = point
 
 
@@ -4521,7 +4532,7 @@ class Excellon(Geometry):
                 else:
                 else:
                     result = float(number_str) / (10 ** (float(nr_length) - float(self.excellon_format_upper_mm)))
                     result = float(number_str) / (10 ** (float(nr_length) - float(self.excellon_format_upper_mm)))
                 return result
                 return result
-            elif self.zeros == "T" or self.zeros == "TZ":  # Trailing
+            else:  # Trailing
                 # You must show all zeros to the right of the number and can omit
                 # You must show all zeros to the right of the number and can omit
                 # all zeros to the left of the number. The CNC-7 will count the number
                 # all zeros to the left of the number. The CNC-7 will count the number
                 # of digits you typed and automatically fill in the missing zeros.
                 # of digits you typed and automatically fill in the missing zeros.
@@ -4533,9 +4544,6 @@ class Excellon(Geometry):
                 else:   # Metric is 000.000
                 else:   # Metric is 000.000
                     result = float(number_str) / (10 ** (float(self.excellon_format_lower_mm)))
                     result = float(number_str) / (10 ** (float(self.excellon_format_lower_mm)))
                 return result
                 return result
-            else: # None - the numbers are in decimal format
-                return float(number_str)
-
         except Exception as e:
         except Exception as e:
             log.error("Aborted. Operation could not be completed due of %s" % str(e))
             log.error("Aborted. Operation could not be completed due of %s" % str(e))
             return
             return
@@ -4631,7 +4639,7 @@ class Excellon(Geometry):
         # fixed issue of getting bounds only for one level lists of objects
         # fixed issue of getting bounds only for one level lists of objects
         # now it can get bounds for nested lists of objects
         # now it can get bounds for nested lists of objects
 
 
-        log.debug("Excellon() -> bounds()")
+        log.debug("camlib.Excellon.bounds()")
         # if self.solid_geometry is None:
         # if self.solid_geometry is None:
         #     log.debug("solid_geometry is None")
         #     log.debug("solid_geometry is None")
         #     return 0, 0, 0, 0
         #     return 0, 0, 0, 0
@@ -4691,6 +4699,8 @@ class Excellon(Geometry):
         :type str: IN or MM
         :type str: IN or MM
         :return:
         :return:
         """
         """
+        log.debug("camlib.Excellon.convert_units()")
+
         factor = Geometry.convert_units(self, units)
         factor = Geometry.convert_units(self, units)
 
 
         # Tools
         # Tools
@@ -4711,6 +4721,8 @@ class Excellon(Geometry):
         :return: None
         :return: None
         :rtype: NOne
         :rtype: NOne
         """
         """
+        log.debug("camlib.Excellon.scale()")
+
         if yfactor is None:
         if yfactor is None:
             yfactor = xfactor
             yfactor = xfactor
 
 
@@ -4755,6 +4767,7 @@ class Excellon(Geometry):
         :type vect: tuple
         :type vect: tuple
         :return: None
         :return: None
         """
         """
+        log.debug("camlib.Excellon.offset()")
 
 
         dx, dy = vect
         dx, dy = vect
 
 
@@ -4795,6 +4808,8 @@ class Excellon(Geometry):
         :type point: list
         :type point: list
         :return: None
         :return: None
         """
         """
+        log.debug("camlib.Excellon.mirror()")
+
         px, py = point
         px, py = point
         xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
         xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
 
 
@@ -4842,6 +4857,8 @@ class Excellon(Geometry):
         See shapely manual for more information:
         See shapely manual for more information:
         http://toblerity.org/shapely/manual.html#affine-transformations
         http://toblerity.org/shapely/manual.html#affine-transformations
         """
         """
+        log.debug("camlib.Excellon.skew()")
+
         if angle_x is None:
         if angle_x is None:
             angle_x = 0.0
             angle_x = 0.0
 
 
@@ -4900,6 +4917,7 @@ class Excellon(Geometry):
         :param point: tuple of coordinates (x, y)
         :param point: tuple of coordinates (x, y)
         :return:
         :return:
         """
         """
+        log.debug("camlib.Excellon.rotate()")
 
 
         def rotate_geom(obj, origin=None):
         def rotate_geom(obj, origin=None):
             if type(obj) is list:
             if type(obj) is list:
@@ -5092,8 +5110,9 @@ class CNCjob(Geometry):
         return self.__dict__
         return self.__dict__
 
 
     def convert_units(self, units):
     def convert_units(self, units):
+        log.debug("camlib.CNCJob.convert_units()")
+
         factor = Geometry.convert_units(self, units)
         factor = Geometry.convert_units(self, units)
-        log.debug("CNCjob.convert_units()")
 
 
         self.z_cut = float(self.z_cut) * factor
         self.z_cut = float(self.z_cut) * factor
         self.z_move *= factor
         self.z_move *= factor
@@ -6987,6 +7006,8 @@ class CNCjob(Geometry):
         # fixed issue of getting bounds only for one level lists of objects
         # fixed issue of getting bounds only for one level lists of objects
         # now it can get bounds for nested lists of objects
         # now it can get bounds for nested lists of objects
 
 
+        log.debug("camlib.CNCJob.bounds()")
+
         def bounds_rec(obj):
         def bounds_rec(obj):
             if type(obj) is list:
             if type(obj) is list:
                 minx = Inf
                 minx = Inf
@@ -7058,6 +7079,7 @@ class CNCjob(Geometry):
         :return: None
         :return: None
         :rtype: None
         :rtype: None
         """
         """
+        log.debug("camlib.CNCJob.scale()")
 
 
         if yfactor is None:
         if yfactor is None:
             yfactor = xfactor
             yfactor = xfactor
@@ -7207,6 +7229,8 @@ class CNCjob(Geometry):
         :type vect: tuple
         :type vect: tuple
         :return: None
         :return: None
         """
         """
+        log.debug("camlib.CNCJob.offset()")
+
         dx, dy = vect
         dx, dy = vect
 
 
         def offset_g(g):
         def offset_g(g):
@@ -7271,6 +7295,8 @@ class CNCjob(Geometry):
         :param point: tupple of coordinates (x,y)
         :param point: tupple of coordinates (x,y)
         :return:
         :return:
         """
         """
+        log.debug("camlib.CNCJob.mirror()")
+
         px, py = point
         px, py = point
         xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
         xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
 
 
@@ -7296,6 +7322,8 @@ class CNCjob(Geometry):
         See shapely manual for more information:
         See shapely manual for more information:
         http://toblerity.org/shapely/manual.html#affine-transformations
         http://toblerity.org/shapely/manual.html#affine-transformations
         """
         """
+        log.debug("camlib.CNCJob.skew()")
+
         px, py = point
         px, py = point
 
 
         for g in self.gcode_parsed:
         for g in self.gcode_parsed:
@@ -7312,6 +7340,7 @@ class CNCjob(Geometry):
         :param point: tupple of coordinates (x,y)
         :param point: tupple of coordinates (x,y)
         :return:
         :return:
         """
         """
+        log.debug("camlib.CNCJob.rotate()")
 
 
         px, py = point
         px, py = point
 
 

+ 1 - 1
config/configuration.txt

@@ -1 +1 @@
-portable=False
+portable=False

+ 19 - 13
flatcamEditors/FlatCAMExcEditor.py

@@ -1251,6 +1251,9 @@ class FCDrillSelect(DrawTool):
         self.storage = self.exc_editor_app.storage_dict
         self.storage = self.exc_editor_app.storage_dict
         # self.selected = self.exc_editor_app.selected
         # self.selected = self.exc_editor_app.selected
 
 
+        # here we store the selected tools
+        self.sel_tools = set()
+
         # here we store all shapes that were selected so we can search for the nearest to our click location
         # here we store all shapes that were selected so we can search for the nearest to our click location
         self.sel_storage = FlatCAMExcEditor.make_storage()
         self.sel_storage = FlatCAMExcEditor.make_storage()
 
 
@@ -1261,16 +1264,18 @@ class FCDrillSelect(DrawTool):
 
 
     def click(self, point):
     def click(self, point):
         key_modifier = QtWidgets.QApplication.keyboardModifiers()
         key_modifier = QtWidgets.QApplication.keyboardModifiers()
-        if self.exc_editor_app.app.defaults["global_mselect_key"] == 'Control':
-            if key_modifier == Qt.ControlModifier:
-                pass
-            else:
-                self.exc_editor_app.selected = []
+
+        if key_modifier == QtCore.Qt.ShiftModifier:
+            mod_key = 'Shift'
+        elif key_modifier == QtCore.Qt.ControlModifier:
+            mod_key = 'Control'
         else:
         else:
-            if key_modifier == Qt.ShiftModifier:
-                pass
-            else:
-                self.exc_editor_app.selected = []
+            mod_key = None
+
+        if mod_key == self.draw_app.app.defaults["global_mselect_key"]:
+            pass
+        else:
+            self.exc_editor_app.selected = []
 
 
     def click_release(self, pos):
     def click_release(self, pos):
         self.exc_editor_app.tools_table_exc.clearSelection()
         self.exc_editor_app.tools_table_exc.clearSelection()
@@ -1308,11 +1313,13 @@ class FCDrillSelect(DrawTool):
             self.exc_editor_app.selected = []
             self.exc_editor_app.selected = []
         else:
         else:
             modifiers = QtWidgets.QApplication.keyboardModifiers()
             modifiers = QtWidgets.QApplication.keyboardModifiers()
-            mod_key = 'Control'
+
             if modifiers == QtCore.Qt.ShiftModifier:
             if modifiers == QtCore.Qt.ShiftModifier:
                 mod_key = 'Shift'
                 mod_key = 'Shift'
             elif modifiers == QtCore.Qt.ControlModifier:
             elif modifiers == QtCore.Qt.ControlModifier:
                 mod_key = 'Control'
                 mod_key = 'Control'
+            else:
+                mod_key = None
 
 
             if mod_key == self.draw_app.app.defaults["global_mselect_key"]:
             if mod_key == self.draw_app.app.defaults["global_mselect_key"]:
                 if closest_shape in self.exc_editor_app.selected:
                 if closest_shape in self.exc_editor_app.selected:
@@ -1329,14 +1336,13 @@ class FCDrillSelect(DrawTool):
             except (TypeError, AttributeError):
             except (TypeError, AttributeError):
                 pass
                 pass
 
 
-            sel_tools = set()
             self.exc_editor_app.tools_table_exc.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
             self.exc_editor_app.tools_table_exc.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
             for shape_s in self.exc_editor_app.selected:
             for shape_s in self.exc_editor_app.selected:
                 for storage in self.exc_editor_app.storage_dict:
                 for storage in self.exc_editor_app.storage_dict:
                     if shape_s in self.exc_editor_app.storage_dict[storage].get_objects():
                     if shape_s in self.exc_editor_app.storage_dict[storage].get_objects():
-                        sel_tools.add(storage)
+                        self.sel_tools.add(storage)
 
 
-            for storage in sel_tools:
+            for storage in self.sel_tools:
                 for k, v in self.draw_app.tool2tooldia.items():
                 for k, v in self.draw_app.tool2tooldia.items():
                     if v == storage:
                     if v == storage:
                         self.exc_editor_app.tools_table_exc.selectRow(int(k) - 1)
                         self.exc_editor_app.tools_table_exc.selectRow(int(k) - 1)

+ 89 - 93
flatcamEditors/FlatCAMGeoEditor.py

@@ -76,7 +76,9 @@ class BufferSelectionTool(FlatCAMTool):
         self.buffer_tools_box.addLayout(form_layout)
         self.buffer_tools_box.addLayout(form_layout)
 
 
         # Buffer distance
         # Buffer distance
-        self.buffer_distance_entry = FCEntry()
+        self.buffer_distance_entry = FCDoubleSpinner()
+        self.buffer_distance_entry.set_precision(4)
+        self.buffer_distance_entry.set_range(0.0000, 999999.9999)
         form_layout.addRow(_("Buffer distance:"), self.buffer_distance_entry)
         form_layout.addRow(_("Buffer distance:"), self.buffer_distance_entry)
         self.buffer_corner_lbl = QtWidgets.QLabel(_("Buffer corner:"))
         self.buffer_corner_lbl = QtWidgets.QLabel(_("Buffer corner:"))
         self.buffer_corner_lbl.setToolTip(
         self.buffer_corner_lbl.setToolTip(
@@ -2358,10 +2360,6 @@ class FCSelect(DrawTool):
 
 
     def click_release(self, point):
     def click_release(self, point):
 
 
-        self.select_shapes(point)
-        return ""
-
-    def select_shapes(self, pos):
         # list where we store the overlapped shapes under our mouse left click position
         # list where we store the overlapped shapes under our mouse left click position
         over_shape_list = []
         over_shape_list = []
 
 
@@ -2381,7 +2379,7 @@ class FCSelect(DrawTool):
 
 
             # 3rd method of click selection -> inconvenient
             # 3rd method of click selection -> inconvenient
             try:
             try:
-                _, closest_shape = self.storage.nearest(pos)
+                _, closest_shape = self.storage.nearest(point)
             except StopIteration:
             except StopIteration:
                 return ""
                 return ""
 
 
@@ -2400,30 +2398,28 @@ class FCSelect(DrawTool):
                 obj_to_add = over_shape_list[int(FlatCAMGeoEditor.draw_shape_idx)]
                 obj_to_add = over_shape_list[int(FlatCAMGeoEditor.draw_shape_idx)]
 
 
                 key_modifier = QtWidgets.QApplication.keyboardModifiers()
                 key_modifier = QtWidgets.QApplication.keyboardModifiers()
-                if self.draw_app.app.defaults["global_mselect_key"] == 'Control':
-                    # if CONTROL key is pressed then we add to the selected list the current shape but if it's already
+
+                if key_modifier == QtCore.Qt.ShiftModifier:
+                    mod_key = 'Shift'
+                elif key_modifier == QtCore.Qt.ControlModifier:
+                    mod_key = 'Control'
+                else:
+                    mod_key = None
+
+                if mod_key == self.draw_app.app.defaults["global_mselect_key"]:
+                    # if modifier key is pressed then we add to the selected list the current shape but if it's already
                     # in the selected list, we removed it. Therefore first click selects, second deselects.
                     # in the selected list, we removed it. Therefore first click selects, second deselects.
-                    if key_modifier == Qt.ControlModifier:
-                        if obj_to_add in self.draw_app.selected:
-                            self.draw_app.selected.remove(obj_to_add)
-                        else:
-                            self.draw_app.selected.append(obj_to_add)
+                    if obj_to_add in self.draw_app.selected:
+                        self.draw_app.selected.remove(obj_to_add)
                     else:
                     else:
-                        self.draw_app.selected = []
                         self.draw_app.selected.append(obj_to_add)
                         self.draw_app.selected.append(obj_to_add)
                 else:
                 else:
-                    if key_modifier == Qt.ShiftModifier:
-                        if obj_to_add in self.draw_app.selected:
-                            self.draw_app.selected.remove(obj_to_add)
-                        else:
-                            self.draw_app.selected.append(obj_to_add)
-                    else:
-                        self.draw_app.selected = []
-                        self.draw_app.selected.append(obj_to_add)
-
+                    self.draw_app.selected = []
+                    self.draw_app.selected.append(obj_to_add)
         except Exception as e:
         except Exception as e:
             log.error("[ERROR] Something went bad. %s" % str(e))
             log.error("[ERROR] Something went bad. %s" % str(e))
             raise
             raise
+        return ""
 
 
 
 
 class FCMove(FCShapeTool):
 class FCMove(FCShapeTool):
@@ -2708,11 +2704,13 @@ class FCBuffer(FCShapeTool):
         # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment
         # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment
         # I populated the combobox such that the index coincide with the join styles value (whcih is really an INT)
         # I populated the combobox such that the index coincide with the join styles value (whcih is really an INT)
         join_style = self.buff_tool.buffer_corner_cb.currentIndex() + 1
         join_style = self.buff_tool.buffer_corner_cb.currentIndex() + 1
-        self.draw_app.buffer(buffer_distance, join_style)
+        ret_val = self.draw_app.buffer(buffer_distance, join_style)
         self.app.ui.notebook.setTabText(2, _("Tools"))
         self.app.ui.notebook.setTabText(2, _("Tools"))
         self.draw_app.app.ui.splitter.setSizes([0, 1])
         self.draw_app.app.ui.splitter.setSizes([0, 1])
 
 
         self.disactivate()
         self.disactivate()
+        if ret_val == 'fail':
+            return
         self.draw_app.app.inform.emit(_("[success] Done. Buffer Tool completed."))
         self.draw_app.app.inform.emit(_("[success] Done. Buffer Tool completed."))
 
 
     def on_buffer_int(self):
     def on_buffer_int(self):
@@ -2734,11 +2732,13 @@ class FCBuffer(FCShapeTool):
         # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment
         # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment
         # I populated the combobox such that the index coincide with the join styles value (whcih is really an INT)
         # I populated the combobox such that the index coincide with the join styles value (whcih is really an INT)
         join_style = self.buff_tool.buffer_corner_cb.currentIndex() + 1
         join_style = self.buff_tool.buffer_corner_cb.currentIndex() + 1
-        self.draw_app.buffer_int(buffer_distance, join_style)
+        ret_val = self.draw_app.buffer_int(buffer_distance, join_style)
         self.app.ui.notebook.setTabText(2, _("Tools"))
         self.app.ui.notebook.setTabText(2, _("Tools"))
         self.draw_app.app.ui.splitter.setSizes([0, 1])
         self.draw_app.app.ui.splitter.setSizes([0, 1])
 
 
         self.disactivate()
         self.disactivate()
+        if ret_val == 'fail':
+            return
         self.draw_app.app.inform.emit(_("[success] Done. Buffer Int Tool completed."))
         self.draw_app.app.inform.emit(_("[success] Done. Buffer Int Tool completed."))
 
 
     def on_buffer_ext(self):
     def on_buffer_ext(self):
@@ -2760,11 +2760,13 @@ class FCBuffer(FCShapeTool):
         # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment
         # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment
         # I populated the combobox such that the index coincide with the join styles value (whcih is really an INT)
         # I populated the combobox such that the index coincide with the join styles value (whcih is really an INT)
         join_style = self.buff_tool.buffer_corner_cb.currentIndex() + 1
         join_style = self.buff_tool.buffer_corner_cb.currentIndex() + 1
-        self.draw_app.buffer_ext(buffer_distance, join_style)
+        ret_val = self.draw_app.buffer_ext(buffer_distance, join_style)
         self.app.ui.notebook.setTabText(2, _("Tools"))
         self.app.ui.notebook.setTabText(2, _("Tools"))
         self.draw_app.app.ui.splitter.setSizes([0, 1])
         self.draw_app.app.ui.splitter.setSizes([0, 1])
 
 
         self.disactivate()
         self.disactivate()
+        if ret_val == 'fail':
+            return
         self.draw_app.app.inform.emit(_("[success] Done. Buffer Ext Tool completed."))
         self.draw_app.app.inform.emit(_("[success] Done. Buffer Ext Tool completed."))
 
 
     def activate(self):
     def activate(self):
@@ -2922,9 +2924,9 @@ class FCTransform(FCShapeTool):
         self.draw_app.transform_tool.run()
         self.draw_app.transform_tool.run()
 
 
 
 
-# ##################### ##
-# # ## Main Application # ##
-# ##################### ##
+# ###############################################
+# ################ Main Application #############
+# ###############################################
 class FlatCAMGeoEditor(QtCore.QObject):
 class FlatCAMGeoEditor(QtCore.QObject):
 
 
     transform_complete = QtCore.pyqtSignal()
     transform_complete = QtCore.pyqtSignal()
@@ -4230,11 +4232,11 @@ class FlatCAMGeoEditor(QtCore.QObject):
             # deselect everything
             # deselect everything
             self.selected = []
             self.selected = []
             self.replot()
             self.replot()
-            return
+            return 'fail'
 
 
         if len(selected) == 0:
         if len(selected) == 0:
             self.app.inform.emit(_("[WARNING_NOTCL] Nothing selected for buffering."))
             self.app.inform.emit(_("[WARNING_NOTCL] Nothing selected for buffering."))
-            return
+            return 'fail'
 
 
         if not isinstance(buf_distance, float):
         if not isinstance(buf_distance, float):
             self.app.inform.emit(_("[WARNING_NOTCL] Invalid distance for buffering."))
             self.app.inform.emit(_("[WARNING_NOTCL] Invalid distance for buffering."))
@@ -4242,17 +4244,32 @@ class FlatCAMGeoEditor(QtCore.QObject):
             # deselect everything
             # deselect everything
             self.selected = []
             self.selected = []
             self.replot()
             self.replot()
-            return
+            return 'fail'
+
+        results = []
+        for t in selected:
+            if isinstance(t.geo, Polygon) and not t.geo.is_empty:
+                results.append((t.geo.exterior).buffer(
+                    buf_distance - 1e-10,
+                    resolution=int(int(self.app.defaults["geometry_circle_steps"]) / 4),
+                    join_style=join_style)
+                )
+            else:
+                results.append(t.geo.buffer(
+                    buf_distance - 1e-10,
+                    resolution=int(int(self.app.defaults["geometry_circle_steps"]) / 4),
+                    join_style=join_style)
+                )
 
 
-        pre_buffer = cascaded_union([t.geo for t in selected])
-        results = pre_buffer.buffer(buf_distance - 1e-10, resolution=32, join_style=join_style)
-        if results.is_empty:
+        if not results:
             self.app.inform.emit(_("[ERROR_NOTCL] Failed, the result is empty. Choose a different buffer value."))
             self.app.inform.emit(_("[ERROR_NOTCL] Failed, the result is empty. Choose a different buffer value."))
             # deselect everything
             # deselect everything
             self.selected = []
             self.selected = []
             self.replot()
             self.replot()
-            return
-        self.add_shape(DrawToolShape(results))
+            return 'fail'
+
+        for sha in results:
+            self.add_shape(DrawToolShape(sha))
 
 
         self.replot()
         self.replot()
         self.app.inform.emit(_("[success] Full buffer geometry created."))
         self.app.inform.emit(_("[success] Full buffer geometry created."))
@@ -4262,77 +4279,48 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
 
         if buf_distance < 0:
         if buf_distance < 0:
             self.app.inform.emit(
             self.app.inform.emit(
-                _("[ERROR_NOTCL] Negative buffer value is not accepted. "
-                  "Use Buffer interior to generate an 'inside' shape")
+                _("[ERROR_NOTCL] Negative buffer value is not accepted.")
             )
             )
             # deselect everything
             # deselect everything
             self.selected = []
             self.selected = []
             self.replot()
             self.replot()
-            return
+            return 'fail'
 
 
         if len(selected) == 0:
         if len(selected) == 0:
             self.app.inform.emit(_("[WARNING_NOTCL] Nothing selected for buffering."))
             self.app.inform.emit(_("[WARNING_NOTCL] Nothing selected for buffering."))
-            return
+            return 'fail'
 
 
         if not isinstance(buf_distance, float):
         if not isinstance(buf_distance, float):
             self.app.inform.emit(_("[WARNING_NOTCL] Invalid distance for buffering."))
             self.app.inform.emit(_("[WARNING_NOTCL] Invalid distance for buffering."))
             # deselect everything
             # deselect everything
             self.selected = []
             self.selected = []
             self.replot()
             self.replot()
-            return
+            return 'fail'
 
 
-        pre_buffer = cascaded_union([t.geo for t in selected])
-        results = pre_buffer.buffer(buf_distance + 1e-10, resolution=32, join_style=join_style)
+        results = []
+        for t in selected:
+            if isinstance(t.geo, LinearRing):
+                t.geo = Polygon(t.geo)
+
+            if isinstance(t.geo, Polygon) and not t.geo.is_empty:
+                results.append((t.geo).buffer(
+                    -buf_distance + 1e-10,
+                    resolution=int(int(self.app.defaults["geometry_circle_steps"]) / 4),
+                    join_style=join_style)
+                )
 
 
-        if results.is_empty:
+        if not results:
             self.app.inform.emit(_("[ERROR_NOTCL] Failed, the result is empty. Choose a smaller buffer value."))
             self.app.inform.emit(_("[ERROR_NOTCL] Failed, the result is empty. Choose a smaller buffer value."))
             # deselect everything
             # deselect everything
             self.selected = []
             self.selected = []
             self.replot()
             self.replot()
-            return
+            return 'fail'
 
 
-        if type(results) == MultiPolygon:
-            for poly in results:
-                for interior in poly.interiors:
-                    self.add_shape(DrawToolShape(interior))
-        else:
-            for interior in results:
-                self.add_shape(DrawToolShape(interior))
+        for sha in results:
+            self.add_shape(DrawToolShape(sha))
 
 
         self.replot()
         self.replot()
         self.app.inform.emit(_("[success] Interior buffer geometry created."))
         self.app.inform.emit(_("[success] Interior buffer geometry created."))
-        # selected = self.get_selected()
-        #
-        # if len(selected) == 0:
-        #     self.app.inform.emit("[WARNING] Nothing selected for buffering.")
-        #     return
-        #
-        # if not isinstance(buf_distance, float):
-        #     self.app.inform.emit("[WARNING] Invalid distance for buffering.")
-        #     return
-        #
-        # pre_buffer = cascaded_union([t.geo for t in selected])
-        # results = pre_buffer.buffer(buf_distance)
-        # if results.is_empty:
-        #     self.app.inform.emit("Failed. Choose a smaller buffer value.")
-        #     return
-        #
-        # int_geo = []
-        # if type(results) == MultiPolygon:
-        #     for poly in results:
-        #         for g in poly.interiors:
-        #             int_geo.append(g)
-        #         res = cascaded_union(int_geo)
-        #         self.add_shape(DrawToolShape(res))
-        # else:
-        #     print(results.interiors)
-        #     for g in results.interiors:
-        #         int_geo.append(g)
-        #     res = cascaded_union(int_geo)
-        #     self.add_shape(DrawToolShape(res))
-        #
-        # self.replot()
-        # self.app.inform.emit("Interior buffer geometry created.")
 
 
     def buffer_ext(self, buf_distance, join_style):
     def buffer_ext(self, buf_distance, join_style):
         selected = self.get_selected()
         selected = self.get_selected()
@@ -4356,19 +4344,27 @@ class FlatCAMGeoEditor(QtCore.QObject):
             self.replot()
             self.replot()
             return
             return
 
 
-        pre_buffer = cascaded_union([t.geo for t in selected])
-        results = pre_buffer.buffer(buf_distance - 1e-10, resolution=32, join_style=join_style)
-        if results.is_empty:
+        results = []
+        for t in selected:
+            if isinstance(t.geo, LinearRing):
+                t.geo = Polygon(t.geo)
+
+            if isinstance(t.geo, Polygon) and not t.geo.is_empty:
+                results.append((t.geo).buffer(
+                    buf_distance,
+                    resolution=int(int(self.app.defaults["geometry_circle_steps"]) / 4),
+                    join_style=join_style)
+                )
+
+        if not results:
             self.app.inform.emit(_("[ERROR_NOTCL] Failed, the result is empty. Choose a different buffer value."))
             self.app.inform.emit(_("[ERROR_NOTCL] Failed, the result is empty. Choose a different buffer value."))
             # deselect everything
             # deselect everything
             self.selected = []
             self.selected = []
             self.replot()
             self.replot()
             return
             return
-        if type(results) == MultiPolygon:
-            for poly in results:
-                self.add_shape(DrawToolShape(poly.exterior))
-        else:
-            self.add_shape(DrawToolShape(results.exterior))
+
+        for sha in results:
+            self.add_shape(DrawToolShape(sha))
 
 
         self.replot()
         self.replot()
         self.app.inform.emit(_("[success] Exterior buffer geometry created."))
         self.app.inform.emit(_("[success] Exterior buffer geometry created."))

+ 64 - 55
flatcamEditors/FlatCAMGrbEditor.py

@@ -2195,6 +2195,9 @@ class FCApertureSelect(DrawTool):
         # bending modes using in FCRegion and FCTrack
         # bending modes using in FCRegion and FCTrack
         self.draw_app.bend_mode = 1
         self.draw_app.bend_mode = 1
 
 
+        # here store the selected apertures
+        self.sel_aperture = set()
+
         try:
         try:
             self.grb_editor_app.apertures_table.clearSelection()
             self.grb_editor_app.apertures_table.clearSelection()
         except Exception as e:
         except Exception as e:
@@ -2202,6 +2205,7 @@ class FCApertureSelect(DrawTool):
 
 
         self.grb_editor_app.hide_tool('all')
         self.grb_editor_app.hide_tool('all')
         self.grb_editor_app.hide_tool('select')
         self.grb_editor_app.hide_tool('select')
+        self.grb_editor_app.array_frame.hide()
 
 
         try:
         try:
             QtGui.QGuiApplication.restoreOverrideCursor()
             QtGui.QGuiApplication.restoreOverrideCursor()
@@ -2213,42 +2217,47 @@ class FCApertureSelect(DrawTool):
 
 
     def click(self, point):
     def click(self, point):
         key_modifier = QtWidgets.QApplication.keyboardModifiers()
         key_modifier = QtWidgets.QApplication.keyboardModifiers()
-        if self.grb_editor_app.app.defaults["global_mselect_key"] == 'Control':
-            if key_modifier == Qt.ControlModifier:
-                pass
-            else:
-                self.grb_editor_app.selected = []
+
+        if key_modifier == QtCore.Qt.ShiftModifier:
+            mod_key = 'Shift'
+        elif key_modifier == QtCore.Qt.ControlModifier:
+            mod_key = 'Control'
         else:
         else:
-            if key_modifier == Qt.ShiftModifier:
-                pass
-            else:
-                self.grb_editor_app.selected = []
+            mod_key = None
+
+        if mod_key == self.draw_app.app.defaults["global_mselect_key"]:
+            pass
+        else:
+            self.grb_editor_app.selected = []
 
 
     def click_release(self, point):
     def click_release(self, point):
         self.grb_editor_app.apertures_table.clearSelection()
         self.grb_editor_app.apertures_table.clearSelection()
-        sel_aperture = set()
         key_modifier = QtWidgets.QApplication.keyboardModifiers()
         key_modifier = QtWidgets.QApplication.keyboardModifiers()
 
 
+        if key_modifier == QtCore.Qt.ShiftModifier:
+            mod_key = 'Shift'
+        elif key_modifier == QtCore.Qt.ControlModifier:
+            mod_key = 'Control'
+        else:
+            mod_key = None
+
         for storage in self.grb_editor_app.storage_dict:
         for storage in self.grb_editor_app.storage_dict:
             try:
             try:
                 for geo_el in self.grb_editor_app.storage_dict[storage]['geometry']:
                 for geo_el in self.grb_editor_app.storage_dict[storage]['geometry']:
                     if 'solid' in geo_el.geo:
                     if 'solid' in geo_el.geo:
                         geometric_data = geo_el.geo['solid']
                         geometric_data = geo_el.geo['solid']
                         if Point(point).within(geometric_data):
                         if Point(point).within(geometric_data):
-                            if (self.grb_editor_app.app.defaults["global_mselect_key"] == 'Control' and
-                                key_modifier == Qt.ControlModifier) or \
-                                    (self.grb_editor_app.app.defaults["global_mselect_key"] == 'Shift' and
-                                     key_modifier == Qt.ShiftModifier):
-
+                            if mod_key == self.grb_editor_app.app.defaults["global_mselect_key"]:
                                 if geo_el in self.draw_app.selected:
                                 if geo_el in self.draw_app.selected:
                                     self.draw_app.selected.remove(geo_el)
                                     self.draw_app.selected.remove(geo_el)
+                                    self.sel_aperture.remove(storage)
                                 else:
                                 else:
                                     # add the object to the selected shapes
                                     # add the object to the selected shapes
                                     self.draw_app.selected.append(geo_el)
                                     self.draw_app.selected.append(geo_el)
-                                    sel_aperture.add(storage)
+                                    self.sel_aperture.add(storage)
                             else:
                             else:
                                 self.draw_app.selected.append(geo_el)
                                 self.draw_app.selected.append(geo_el)
-                                sel_aperture.add(storage)
+                                self.sel_aperture.add(storage)
             except KeyError:
             except KeyError:
                 pass
                 pass
 
 
@@ -2259,7 +2268,7 @@ class FCApertureSelect(DrawTool):
             log.debug("FlatCAMGrbEditor.FCApertureSelect.click_release() --> %s" % str(e))
             log.debug("FlatCAMGrbEditor.FCApertureSelect.click_release() --> %s" % str(e))
 
 
         self.grb_editor_app.apertures_table.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
         self.grb_editor_app.apertures_table.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
-        for aper in sel_aperture:
+        for aper in self.sel_aperture:
             for row in range(self.grb_editor_app.apertures_table.rowCount()):
             for row in range(self.grb_editor_app.apertures_table.rowCount()):
                 if str(aper) == self.grb_editor_app.apertures_table.item(row, 1).text():
                 if str(aper) == self.grb_editor_app.apertures_table.item(row, 1).text():
                     self.grb_editor_app.apertures_table.selectRow(row)
                     self.grb_editor_app.apertures_table.selectRow(row)
@@ -2345,7 +2354,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         # #########################
         # #########################
         # ### Gerber Apertures ####
         # ### Gerber Apertures ####
         # #########################
         # #########################
-        self.apertures_table_label = QtWidgets.QLabel(_('<b>Apertures:</b>'))
+        self.apertures_table_label = QtWidgets.QLabel('<b>%s:</b>' % _('Apertures'))
         self.apertures_table_label.setToolTip(
         self.apertures_table_label.setToolTip(
             _("Apertures Table for the Gerber Object.")
             _("Apertures Table for the Gerber Object.")
         )
         )
@@ -2391,7 +2400,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         grid1 = QtWidgets.QGridLayout()
         grid1 = QtWidgets.QGridLayout()
         self.apertures_box.addLayout(grid1)
         self.apertures_box.addLayout(grid1)
 
 
-        apcode_lbl = QtWidgets.QLabel(_('Aperture Code:'))
+        apcode_lbl = QtWidgets.QLabel('%s:' % _('Aperture Code'))
         apcode_lbl.setToolTip(
         apcode_lbl.setToolTip(
         _("Code for the new aperture")
         _("Code for the new aperture")
         )
         )
@@ -2401,7 +2410,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.apcode_entry.setValidator(QtGui.QIntValidator(0, 999))
         self.apcode_entry.setValidator(QtGui.QIntValidator(0, 999))
         grid1.addWidget(self.apcode_entry, 1, 1)
         grid1.addWidget(self.apcode_entry, 1, 1)
 
 
-        apsize_lbl = QtWidgets.QLabel(_('Aperture Size:'))
+        apsize_lbl = QtWidgets.QLabel('%s:' % _('Aperture Size'))
         apsize_lbl.setToolTip(
         apsize_lbl.setToolTip(
         _("Size for the new aperture.\n"
         _("Size for the new aperture.\n"
           "If aperture type is 'R' or 'O' then\n"
           "If aperture type is 'R' or 'O' then\n"
@@ -2415,7 +2424,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.apsize_entry.setValidator(QtGui.QDoubleValidator(0.0001, 99.9999, 4))
         self.apsize_entry.setValidator(QtGui.QDoubleValidator(0.0001, 99.9999, 4))
         grid1.addWidget(self.apsize_entry, 2, 1)
         grid1.addWidget(self.apsize_entry, 2, 1)
 
 
-        aptype_lbl = QtWidgets.QLabel(_('Aperture Type:'))
+        aptype_lbl = QtWidgets.QLabel('%s:' % _('Aperture Type'))
         aptype_lbl.setToolTip(
         aptype_lbl.setToolTip(
         _("Select the type of new aperture. Can be:\n"
         _("Select the type of new aperture. Can be:\n"
           "C = circular\n"
           "C = circular\n"
@@ -2428,7 +2437,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.aptype_cb.addItems(['C', 'R', 'O'])
         self.aptype_cb.addItems(['C', 'R', 'O'])
         grid1.addWidget(self.aptype_cb, 3, 1)
         grid1.addWidget(self.aptype_cb, 3, 1)
 
 
-        self.apdim_lbl = QtWidgets.QLabel(_('Aperture Dim:'))
+        self.apdim_lbl = QtWidgets.QLabel('%s:' % _('Aperture Dim'))
         self.apdim_lbl.setToolTip(
         self.apdim_lbl.setToolTip(
         _("Dimensions for the new aperture.\n"
         _("Dimensions for the new aperture.\n"
           "Active only for rectangular apertures (type R).\n"
           "Active only for rectangular apertures (type R).\n"
@@ -2484,8 +2493,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
 
         # Buffer distance
         # Buffer distance
         self.buffer_distance_entry = FCEntry()
         self.buffer_distance_entry = FCEntry()
-        buf_form_layout.addRow(_("Buffer distance:"), self.buffer_distance_entry)
-        self.buffer_corner_lbl = QtWidgets.QLabel(_("Buffer corner:"))
+        buf_form_layout.addRow('%s:' % _("Buffer distance"), self.buffer_distance_entry)
+        self.buffer_corner_lbl = QtWidgets.QLabel('%s:' % _("Buffer corner"))
         self.buffer_corner_lbl.setToolTip(
         self.buffer_corner_lbl.setToolTip(
             _("There are 3 types of corners:\n"
             _("There are 3 types of corners:\n"
               " - 'Round': the corner is rounded.\n"
               " - 'Round': the corner is rounded.\n"
@@ -2517,7 +2526,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.scale_tool_frame.hide()
         self.scale_tool_frame.hide()
 
 
         # Title
         # Title
-        scale_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _('Scale Aperture:'))
+        scale_title_lbl = QtWidgets.QLabel('<b>%s:</b>' % _('Scale Aperture'))
         scale_title_lbl.setToolTip(
         scale_title_lbl.setToolTip(
             _("Scale a aperture in the aperture list")
             _("Scale a aperture in the aperture list")
         )
         )
@@ -2527,7 +2536,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         scale_form_layout = QtWidgets.QFormLayout()
         scale_form_layout = QtWidgets.QFormLayout()
         self.scale_tools_box.addLayout(scale_form_layout)
         self.scale_tools_box.addLayout(scale_form_layout)
 
 
-        self.scale_factor_lbl = QtWidgets.QLabel(_("Scale factor:"))
+        self.scale_factor_lbl = QtWidgets.QLabel('%s:' % _("Scale factor"))
         self.scale_factor_lbl.setToolTip(
         self.scale_factor_lbl.setToolTip(
             _("The factor by which to scale the selected aperture.\n"
             _("The factor by which to scale the selected aperture.\n"
               "Values can be between 0.0000 and 999.9999")
               "Values can be between 0.0000 and 999.9999")
@@ -2555,7 +2564,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.ma_tool_frame.hide()
         self.ma_tool_frame.hide()
 
 
         # Title
         # Title
-        ma_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _('Mark polygon areas:'))
+        ma_title_lbl = QtWidgets.QLabel('<b>%s:</b>' % _('Mark polygon areas'))
         ma_title_lbl.setToolTip(
         ma_title_lbl.setToolTip(
             _("Mark the polygon areas.")
             _("Mark the polygon areas.")
         )
         )
@@ -2565,7 +2574,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         ma_form_layout = QtWidgets.QFormLayout()
         ma_form_layout = QtWidgets.QFormLayout()
         self.ma_tools_box.addLayout(ma_form_layout)
         self.ma_tools_box.addLayout(ma_form_layout)
 
 
-        self.ma_upper_threshold_lbl = QtWidgets.QLabel(_("Area UPPER threshold:"))
+        self.ma_upper_threshold_lbl = QtWidgets.QLabel('%s:' % _("Area UPPER threshold"))
         self.ma_upper_threshold_lbl.setToolTip(
         self.ma_upper_threshold_lbl.setToolTip(
             _("The threshold value, all areas less than this are marked.\n"
             _("The threshold value, all areas less than this are marked.\n"
               "Can have a value between 0.0000 and 9999.9999")
               "Can have a value between 0.0000 and 9999.9999")
@@ -2573,7 +2582,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.ma_upper_threshold_entry = FCEntry()
         self.ma_upper_threshold_entry = FCEntry()
         self.ma_upper_threshold_entry.setValidator(QtGui.QDoubleValidator(0.0000, 9999.9999, 4))
         self.ma_upper_threshold_entry.setValidator(QtGui.QDoubleValidator(0.0000, 9999.9999, 4))
 
 
-        self.ma_lower_threshold_lbl = QtWidgets.QLabel(_("Area LOWER threshold:"))
+        self.ma_lower_threshold_lbl = QtWidgets.QLabel('%s:' % _("Area LOWER threshold"))
         self.ma_lower_threshold_lbl.setToolTip(
         self.ma_lower_threshold_lbl.setToolTip(
             _("The threshold value, all areas more than this are marked.\n"
             _("The threshold value, all areas more than this are marked.\n"
               "Can have a value between 0.0000 and 9999.9999")
               "Can have a value between 0.0000 and 9999.9999")
@@ -2594,7 +2603,6 @@ class FlatCAMGrbEditor(QtCore.QObject):
         # ######################
         # ######################
         # ### Add Pad Array ####
         # ### Add Pad Array ####
         # ######################
         # ######################
-
         # add a frame and inside add a vertical box layout. Inside this vbox layout I add
         # add a frame and inside add a vertical box layout. Inside this vbox layout I add
         # all the add Pad array  widgets
         # all the add Pad array  widgets
         # this way I can hide/show the frame
         # this way I can hide/show the frame
@@ -2627,7 +2635,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.array_form = QtWidgets.QFormLayout()
         self.array_form = QtWidgets.QFormLayout()
         self.array_box.addLayout(self.array_form)
         self.array_box.addLayout(self.array_form)
 
 
-        self.pad_array_size_label = QtWidgets.QLabel(_('Nr of pads:'))
+        self.pad_array_size_label = QtWidgets.QLabel('%s:' % _('Nr of pads'))
         self.pad_array_size_label.setToolTip(
         self.pad_array_size_label.setToolTip(
             _("Specify how many pads to be in the array.")
             _("Specify how many pads to be in the array.")
         )
         )
@@ -2646,7 +2654,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.linear_form = QtWidgets.QFormLayout()
         self.linear_form = QtWidgets.QFormLayout()
         self.linear_box.addLayout(self.linear_form)
         self.linear_box.addLayout(self.linear_form)
 
 
-        self.pad_axis_label = QtWidgets.QLabel(_('Direction:'))
+        self.pad_axis_label = QtWidgets.QLabel('%s:' % _('Direction'))
         self.pad_axis_label.setToolTip(
         self.pad_axis_label.setToolTip(
             _("Direction on which the linear array is oriented:\n"
             _("Direction on which the linear array is oriented:\n"
               "- 'X' - horizontal axis \n"
               "- 'X' - horizontal axis \n"
@@ -2661,7 +2669,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.pad_axis_radio.set_value('X')
         self.pad_axis_radio.set_value('X')
         self.linear_form.addRow(self.pad_axis_label, self.pad_axis_radio)
         self.linear_form.addRow(self.pad_axis_label, self.pad_axis_radio)
 
 
-        self.pad_pitch_label = QtWidgets.QLabel(_('Pitch:'))
+        self.pad_pitch_label = QtWidgets.QLabel('%s:' % _('Pitch'))
         self.pad_pitch_label.setToolTip(
         self.pad_pitch_label.setToolTip(
             _("Pitch = Distance between elements of the array.")
             _("Pitch = Distance between elements of the array.")
         )
         )
@@ -2670,7 +2678,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.pad_pitch_entry = LengthEntry()
         self.pad_pitch_entry = LengthEntry()
         self.linear_form.addRow(self.pad_pitch_label, self.pad_pitch_entry)
         self.linear_form.addRow(self.pad_pitch_label, self.pad_pitch_entry)
 
 
-        self.linear_angle_label = QtWidgets.QLabel(_('Angle:'))
+        self.linear_angle_label = QtWidgets.QLabel('%s:' % _('Angle'))
         self.linear_angle_label.setToolTip(
         self.linear_angle_label.setToolTip(
            _( "Angle at which the linear array is placed.\n"
            _( "Angle at which the linear array is placed.\n"
               "The precision is of max 2 decimals.\n"
               "The precision is of max 2 decimals.\n"
@@ -2691,7 +2699,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.circular_box.setContentsMargins(0, 0, 0, 0)
         self.circular_box.setContentsMargins(0, 0, 0, 0)
         self.array_circular_frame.setLayout(self.circular_box)
         self.array_circular_frame.setLayout(self.circular_box)
 
 
-        self.pad_direction_label = QtWidgets.QLabel(_('Direction:'))
+        self.pad_direction_label = QtWidgets.QLabel('%s:' % _('Direction'))
         self.pad_direction_label.setToolTip(
         self.pad_direction_label.setToolTip(
            _("Direction for circular array."
            _("Direction for circular array."
              "Can be CW = clockwise or CCW = counter clockwise.")
              "Can be CW = clockwise or CCW = counter clockwise.")
@@ -2706,7 +2714,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.pad_direction_radio.set_value('CW')
         self.pad_direction_radio.set_value('CW')
         self.circular_form.addRow(self.pad_direction_label, self.pad_direction_radio)
         self.circular_form.addRow(self.pad_direction_label, self.pad_direction_radio)
 
 
-        self.pad_angle_label = QtWidgets.QLabel(_('Angle:'))
+        self.pad_angle_label = QtWidgets.QLabel('%s:' % _('Angle'))
         self.pad_angle_label.setToolTip(
         self.pad_angle_label.setToolTip(
             _("Angle at which each element in circular array is placed.")
             _("Angle at which each element in circular array is placed.")
         )
         )
@@ -2944,23 +2952,24 @@ class FlatCAMGrbEditor(QtCore.QObject):
             self.tool2tooldia[i + 1] = tt_aperture
             self.tool2tooldia[i + 1] = tt_aperture
 
 
         # Init GUI
         # Init GUI
-        if self.units == 'MM':
-            self.buffer_distance_entry.set_value(0.01)
-            self.scale_factor_entry.set_value(1.0)
-            self.ma_upper_threshold_entry.set_value(1.0)
-            self.apsize_entry.set_value(1.00)
-        else:
-            self.buffer_distance_entry.set_value(0.0003937)
-            self.scale_factor_entry.set_value(0.03937)
-            self.ma_upper_threshold_entry.set_value(0.00155)
-            self.apsize_entry.set_value(0.039)
-        self.ma_lower_threshold_entry.set_value(0.0)
-
-        self.pad_array_size_entry.set_value(5)
-        self.pad_pitch_entry.set_value(2.54)
-        self.pad_angle_entry.set_value(12)
-        self.pad_direction_radio.set_value('CW')
-        self.pad_axis_radio.set_value('X')
+
+        self.buffer_distance_entry.set_value(self.app.defaults["gerber_editor_buff_f"])
+        self.scale_factor_entry.set_value(self.app.defaults["gerber_editor_scale_f"])
+        self.ma_upper_threshold_entry.set_value(self.app.defaults["gerber_editor_ma_low"])
+        self.ma_lower_threshold_entry.set_value(self.app.defaults["gerber_editor_ma_high"])
+
+        self.apsize_entry.set_value(self.app.defaults["gerber_editor_newsize"])
+        self.aptype_cb.set_value(self.app.defaults["gerber_editor_newtype"])
+        self.apdim_entry.set_value(self.app.defaults["gerber_editor_newdim"])
+
+        self.pad_array_size_entry.set_value(self.app.defaults["gerber_editor_array_size"])
+        # linear array
+        self.pad_axis_radio.set_value(self.app.defaults["gerber_editor_lin_axis"])
+        self.pad_pitch_entry.set_value(self.app.defaults["gerber_editor_lin_pitch"])
+        self.linear_angle_spinner.set_value(self.app.defaults["gerber_editor_lin_angle"])
+        # circular array
+        self.pad_direction_radio.set_value(self.app.defaults["gerber_editor_circ_dir"])
+        self.pad_angle_entry.set_value(self.app.defaults["gerber_editor_circ_angle"])
 
 
     def build_ui(self, first_run=None):
     def build_ui(self, first_run=None):
 
 
@@ -3113,7 +3122,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
             self.apcode_entry.set_value(max(self.tool2tooldia.values()) + 1)
             self.apcode_entry.set_value(max(self.tool2tooldia.values()) + 1)
         except ValueError:
         except ValueError:
             # this means that the edited object has no apertures so we start with 10 (Gerber specifications)
             # this means that the edited object has no apertures so we start with 10 (Gerber specifications)
-            self.apcode_entry.set_value(10)
+            self.apcode_entry.set_value(self.app.defaults["gerber_editor_newcode"])
 
 
     def on_aperture_add(self, apid=None):
     def on_aperture_add(self, apid=None):
         self.is_modified = True
         self.is_modified = True

Plik diff jest za duży
+ 331 - 89
flatcamGUI/FlatCAMGUI.py


+ 126 - 49
flatcamGUI/GUIElements.py

@@ -171,9 +171,11 @@ class LengthEntry(QtWidgets.QLineEdit):
             self.readyToEdit = False
             self.readyToEdit = False
 
 
     def focusOutEvent(self, e):
     def focusOutEvent(self, e):
-        super(LengthEntry, self).focusOutEvent(e)  # required to remove cursor on focusOut
-        self.deselect()
-        self.readyToEdit = True
+        # don't focus out if the user requests an popup menu
+        if e.reason() != QtCore.Qt.PopupFocusReason:
+            super(LengthEntry, self).focusOutEvent(e)  # required to remove cursor on focusOut
+            self.deselect()
+            self.readyToEdit = True
 
 
     def returnPressed(self, *args, **kwargs):
     def returnPressed(self, *args, **kwargs):
         val = self.get_value()
         val = self.get_value()
@@ -225,9 +227,11 @@ class FloatEntry(QtWidgets.QLineEdit):
             self.readyToEdit = False
             self.readyToEdit = False
 
 
     def focusOutEvent(self, e):
     def focusOutEvent(self, e):
-        super(FloatEntry, self).focusOutEvent(e)  # required to remove cursor on focusOut
-        self.deselect()
-        self.readyToEdit = True
+        # don't focus out if the user requests an popup menu
+        if e.reason() != QtCore.Qt.PopupFocusReason:
+            super(FloatEntry, self).focusOutEvent(e)  # required to remove cursor on focusOut
+            self.deselect()
+            self.readyToEdit = True
 
 
     def returnPressed(self, *args, **kwargs):
     def returnPressed(self, *args, **kwargs):
         val = self.get_value()
         val = self.get_value()
@@ -274,9 +278,11 @@ class FloatEntry2(QtWidgets.QLineEdit):
             self.readyToEdit = False
             self.readyToEdit = False
 
 
     def focusOutEvent(self, e):
     def focusOutEvent(self, e):
-        super(FloatEntry2, self).focusOutEvent(e)  # required to remove cursor on focusOut
-        self.deselect()
-        self.readyToEdit = True
+        # don't focus out if the user requests an popup menu
+        if e.reason() != QtCore.Qt.PopupFocusReason:
+            super(FloatEntry2, self).focusOutEvent(e)  # required to remove cursor on focusOut
+            self.deselect()
+            self.readyToEdit = True
 
 
     def get_value(self):
     def get_value(self):
         raw = str(self.text()).strip(' ')
         raw = str(self.text()).strip(' ')
@@ -316,9 +322,11 @@ class IntEntry(QtWidgets.QLineEdit):
             self.readyToEdit = False
             self.readyToEdit = False
 
 
     def focusOutEvent(self, e):
     def focusOutEvent(self, e):
-        super(IntEntry, self).focusOutEvent(e)  # required to remove cursor on focusOut
-        self.deselect()
-        self.readyToEdit = True
+        # don't focus out if the user requests an popup menu
+        if e.reason() != QtCore.Qt.PopupFocusReason:
+            super(IntEntry, self).focusOutEvent(e)  # required to remove cursor on focusOut
+            self.deselect()
+            self.readyToEdit = True
 
 
     def get_value(self):
     def get_value(self):
 
 
@@ -353,16 +361,17 @@ class FCEntry(QtWidgets.QLineEdit):
     def on_edit_finished(self):
     def on_edit_finished(self):
         self.clearFocus()
         self.clearFocus()
 
 
-    def mousePressEvent(self, e, Parent=None):
+    def mousePressEvent(self, e, parent=None):
         super(FCEntry, self).mousePressEvent(e)  # required to deselect on 2e click
         super(FCEntry, self).mousePressEvent(e)  # required to deselect on 2e click
         if self.readyToEdit:
         if self.readyToEdit:
             self.selectAll()
             self.selectAll()
             self.readyToEdit = False
             self.readyToEdit = False
 
 
     def focusOutEvent(self, e):
     def focusOutEvent(self, e):
-        super(FCEntry, self).focusOutEvent(e)  # required to remove cursor on focusOut
-        self.deselect()
-        self.readyToEdit = True
+        if e.reason() != QtCore.Qt.PopupFocusReason:
+            super(FCEntry, self).focusOutEvent(e)  # required to remove cursor on focusOut
+            self.deselect()
+            self.readyToEdit = True
 
 
     def get_value(self):
     def get_value(self):
         return str(self.text())
         return str(self.text())
@@ -381,36 +390,24 @@ class FCEntry(QtWidgets.QLineEdit):
 class FCEntry2(FCEntry):
 class FCEntry2(FCEntry):
     def __init__(self, parent=None):
     def __init__(self, parent=None):
         super(FCEntry2, self).__init__(parent)
         super(FCEntry2, self).__init__(parent)
-        self.readyToEdit = True
-        self.editingFinished.connect(self.on_edit_finished)
-
-    def on_edit_finished(self):
-        self.clearFocus()
 
 
     def set_value(self, val, decimals=4):
     def set_value(self, val, decimals=4):
         try:
         try:
             fval = float(val)
             fval = float(val)
         except ValueError:
         except ValueError:
             return
             return
-
         self.setText('%.*f' % (decimals, fval))
         self.setText('%.*f' % (decimals, fval))
 
 
 
 
 class FCEntry3(FCEntry):
 class FCEntry3(FCEntry):
     def __init__(self, parent=None):
     def __init__(self, parent=None):
         super(FCEntry3, self).__init__(parent)
         super(FCEntry3, self).__init__(parent)
-        self.readyToEdit = True
-        self.editingFinished.connect(self.on_edit_finished)
-
-    def on_edit_finished(self):
-        self.clearFocus()
 
 
     def set_value(self, val, decimals=4):
     def set_value(self, val, decimals=4):
         try:
         try:
             fval = float(val)
             fval = float(val)
         except ValueError:
         except ValueError:
             return
             return
-
         self.setText('%.*f' % (decimals, fval))
         self.setText('%.*f' % (decimals, fval))
 
 
     def get_value(self):
     def get_value(self):
@@ -432,16 +429,17 @@ class EvalEntry(QtWidgets.QLineEdit):
     def on_edit_finished(self):
     def on_edit_finished(self):
         self.clearFocus()
         self.clearFocus()
 
 
-    def mousePressEvent(self, e, Parent=None):
+    def mousePressEvent(self, e, parent=None):
         super(EvalEntry, self).mousePressEvent(e)  # required to deselect on 2e click
         super(EvalEntry, self).mousePressEvent(e)  # required to deselect on 2e click
         if self.readyToEdit:
         if self.readyToEdit:
             self.selectAll()
             self.selectAll()
             self.readyToEdit = False
             self.readyToEdit = False
 
 
     def focusOutEvent(self, e):
     def focusOutEvent(self, e):
-        super(EvalEntry, self).focusOutEvent(e)  # required to remove cursor on focusOut
-        self.deselect()
-        self.readyToEdit = True
+        if e.reason() != QtCore.Qt.PopupFocusReason:
+            super(EvalEntry, self).focusOutEvent(e)  # required to remove cursor on focusOut
+            self.deselect()
+            self.readyToEdit = True
 
 
     def returnPressed(self, *args, **kwargs):
     def returnPressed(self, *args, **kwargs):
         val = self.get_value()
         val = self.get_value()
@@ -478,16 +476,17 @@ class EvalEntry2(QtWidgets.QLineEdit):
     def on_edit_finished(self):
     def on_edit_finished(self):
         self.clearFocus()
         self.clearFocus()
 
 
-    def mousePressEvent(self, e, Parent=None):
+    def mousePressEvent(self, e, parent=None):
         super(EvalEntry2, self).mousePressEvent(e)  # required to deselect on 2e click
         super(EvalEntry2, self).mousePressEvent(e)  # required to deselect on 2e click
         if self.readyToEdit:
         if self.readyToEdit:
             self.selectAll()
             self.selectAll()
             self.readyToEdit = False
             self.readyToEdit = False
 
 
     def focusOutEvent(self, e):
     def focusOutEvent(self, e):
-        super(EvalEntry2, self).focusOutEvent(e)  # required to remove cursor on focusOut
-        self.deselect()
-        self.readyToEdit = True
+        if e.reason() != QtCore.Qt.PopupFocusReason:
+            super(EvalEntry2, self).focusOutEvent(e)  # required to remove cursor on focusOut
+            self.deselect()
+            self.readyToEdit = True
 
 
     def get_value(self):
     def get_value(self):
         raw = str(self.text()).strip(' ')
         raw = str(self.text()).strip(' ')
@@ -847,9 +846,9 @@ class FCDetachableTab(QtWidgets.QTabWidget):
         super().__init__()
         super().__init__()
 
 
         self.tabBar = self.FCTabBar(self)
         self.tabBar = self.FCTabBar(self)
-        self.tabBar.onDetachTabSignal.connect(self.detachTab)
         self.tabBar.onMoveTabSignal.connect(self.moveTab)
         self.tabBar.onMoveTabSignal.connect(self.moveTab)
         self.tabBar.detachedTabDropSignal.connect(self.detachedTabDrop)
         self.tabBar.detachedTabDropSignal.connect(self.detachedTabDrop)
+        self.set_detachable(val=True)
 
 
         self.setTabBar(self.tabBar)
         self.setTabBar(self.tabBar)
 
 
@@ -872,6 +871,48 @@ class FCDetachableTab(QtWidgets.QTabWidget):
         self.setTabsClosable(True)
         self.setTabsClosable(True)
         self.tabCloseRequested.connect(self.closeTab)
         self.tabCloseRequested.connect(self.closeTab)
 
 
+    def set_rmb_callback(self, callback):
+        """
+
+        :param callback: Function to call on right mouse click on tab
+        :type callback: func
+        :return: None
+        """
+
+        self.tabBar.right_click.connect(callback)
+
+    def set_detachable(self, val=True):
+        try:
+            self.tabBar.onDetachTabSignal.disconnect()
+        except TypeError:
+            pass
+
+        if val is True:
+            self.tabBar.onDetachTabSignal.connect(self.detachTab)
+            # the tab can be moved around
+            self.tabBar.can_be_dragged = True
+        else:
+            # the detached tab can't be moved
+            self.tabBar.can_be_dragged = False
+
+        return val
+
+    def setupContextMenu(self):
+        self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
+
+    def addContextMenu(self, entry, call_function, icon=None, initial_checked=False):
+        action_name = str(entry)
+        action = QtWidgets.QAction(self)
+        action.setCheckable(True)
+        action.setText(action_name)
+        if icon:
+            assert isinstance(icon, QtGui.QIcon), \
+                "Expected the argument to be QtGui.QIcon. Instead it is %s" % type(icon)
+            action.setIcon(icon)
+        action.setChecked(initial_checked)
+        self.addAction(action)
+        action.triggered.connect(call_function)
+
     def useOldIndex(self, param):
     def useOldIndex(self, param):
         if param:
         if param:
             self.use_old_index = True
             self.use_old_index = True
@@ -1209,6 +1250,8 @@ class FCDetachableTab(QtWidgets.QTabWidget):
         onMoveTabSignal = pyqtSignal(int, int)
         onMoveTabSignal = pyqtSignal(int, int)
         detachedTabDropSignal = pyqtSignal(str, int, QtCore.QPoint)
         detachedTabDropSignal = pyqtSignal(str, int, QtCore.QPoint)
 
 
+        right_click = pyqtSignal(int)
+
         def __init__(self, parent=None):
         def __init__(self, parent=None):
             QtWidgets.QTabBar.__init__(self, parent)
             QtWidgets.QTabBar.__init__(self, parent)
 
 
@@ -1216,11 +1259,16 @@ class FCDetachableTab(QtWidgets.QTabWidget):
             self.setElideMode(QtCore.Qt.ElideRight)
             self.setElideMode(QtCore.Qt.ElideRight)
             self.setSelectionBehaviorOnRemove(QtWidgets.QTabBar.SelectLeftTab)
             self.setSelectionBehaviorOnRemove(QtWidgets.QTabBar.SelectLeftTab)
 
 
+            self.prev_index = -1
+
             self.dragStartPos = QtCore.QPoint()
             self.dragStartPos = QtCore.QPoint()
             self.dragDropedPos = QtCore.QPoint()
             self.dragDropedPos = QtCore.QPoint()
             self.mouseCursor = QtGui.QCursor()
             self.mouseCursor = QtGui.QCursor()
             self.dragInitiated = False
             self.dragInitiated = False
 
 
+            # set this to False and the tab will no longer be displayed as detached
+            self.can_be_dragged = True
+
         def mouseDoubleClickEvent(self, event):
         def mouseDoubleClickEvent(self, event):
             """
             """
             Send the onDetachTabSignal when a tab is double clicked
             Send the onDetachTabSignal when a tab is double clicked
@@ -1234,21 +1282,37 @@ class FCDetachableTab(QtWidgets.QTabWidget):
 
 
         def mousePressEvent(self, event):
         def mousePressEvent(self, event):
             """
             """
-            Set the starting position for a drag event when the mouse button is pressed
+            Set the starting position for a drag event when the left mouse button is pressed.
+            Start detection of a right mouse click.
 
 
             :param event:   a mouse press event
             :param event:   a mouse press event
             :return:
             :return:
             """
             """
             if event.button() == QtCore.Qt.LeftButton:
             if event.button() == QtCore.Qt.LeftButton:
                 self.dragStartPos = event.pos()
                 self.dragStartPos = event.pos()
+            elif event.button() == QtCore.Qt.RightButton:
+                self.prev_index = self.tabAt(event.pos())
 
 
             self.dragDropedPos.setX(0)
             self.dragDropedPos.setX(0)
             self.dragDropedPos.setY(0)
             self.dragDropedPos.setY(0)
-
             self.dragInitiated = False
             self.dragInitiated = False
 
 
             QtWidgets.QTabBar.mousePressEvent(self, event)
             QtWidgets.QTabBar.mousePressEvent(self, event)
 
 
+        def mouseReleaseEvent(self, event):
+            """
+            Finish the detection of the right mouse click on the tab
+
+
+            :param event:   a mouse press event
+            :return:
+            """
+            if event.button() == QtCore.Qt.RightButton and self.prev_index == self.tabAt(event.pos()):
+                self.right_click.emit(self.prev_index)
+            self.prev_index = -1
+
+            QtWidgets.QTabBar.mouseReleaseEvent(self, event)
+
         def mouseMoveEvent(self, event):
         def mouseMoveEvent(self, event):
             """
             """
             Determine if the current movement is a drag.  If it is, convert it into a QDrag.  If the
             Determine if the current movement is a drag.  If it is, convert it into a QDrag.  If the
@@ -1264,7 +1328,7 @@ class FCDetachableTab(QtWidgets.QTabWidget):
                 self.dragInitiated = True
                 self.dragInitiated = True
 
 
             # If the current movement is a drag initiated by the left button
             # If the current movement is a drag initiated by the left button
-            if (((event.buttons() & QtCore.Qt.LeftButton)) and self.dragInitiated):
+            if (((event.buttons() & QtCore.Qt.LeftButton)) and self.dragInitiated and self.can_be_dragged):
 
 
                 # Stop the move event
                 # Stop the move event
                 finishMoveEvent = QtGui.QMouseEvent(
                 finishMoveEvent = QtGui.QMouseEvent(
@@ -1561,9 +1625,11 @@ class FCSpinner(QtWidgets.QSpinBox):
             self.readyToEdit = False
             self.readyToEdit = False
 
 
     def focusOutEvent(self, e):
     def focusOutEvent(self, e):
-        super(FCSpinner, self).focusOutEvent(e)  # required to remove cursor on focusOut
-        self.lineEdit().deselect()
-        self.readyToEdit = True
+        # don't focus out if the user requests an popup menu
+        if e.reason() != QtCore.Qt.PopupFocusReason:
+            super(FCSpinner, self).focusOutEvent(e)  # required to remove cursor on focusOut
+            self.lineEdit().deselect()
+            self.readyToEdit = True
 
 
     def get_value(self):
     def get_value(self):
         return str(self.value())
         return str(self.value())
@@ -1600,9 +1666,11 @@ class FCDoubleSpinner(QtWidgets.QDoubleSpinBox):
             self.readyToEdit = False
             self.readyToEdit = False
 
 
     def focusOutEvent(self, e):
     def focusOutEvent(self, e):
-        super(FCDoubleSpinner, self).focusOutEvent(e)  # required to remove cursor on focusOut
-        self.lineEdit().deselect()
-        self.readyToEdit = True
+        # don't focus out if the user requests an popup menu
+        if e.reason() != QtCore.Qt.PopupFocusReason:
+            super(FCDoubleSpinner, self).focusOutEvent(e)  # required to remove cursor on focusOut
+            self.lineEdit().deselect()
+            self.readyToEdit = True
 
 
     def get_value(self):
     def get_value(self):
         return str(self.value())
         return str(self.value())
@@ -1647,9 +1715,11 @@ class Dialog_box(QtWidgets.QWidget):
             self.readyToEdit = False
             self.readyToEdit = False
 
 
     def focusOutEvent(self, e):
     def focusOutEvent(self, e):
-        super(Dialog_box, self).focusOutEvent(e)  # required to remove cursor on focusOut
-        self.lineEdit().deselect()
-        self.readyToEdit = True
+        # don't focus out if the user requests an popup menu
+        if e.reason() != QtCore.Qt.PopupFocusReason:
+            super(Dialog_box, self).focusOutEvent(e)  # required to remove cursor on focusOut
+            self.lineEdit().deselect()
+            self.readyToEdit = True
 
 
 
 
 class _BrowserTextEdit(QTextEdit):
 class _BrowserTextEdit(QTextEdit):
@@ -1811,9 +1881,16 @@ class MyCompleter(QCompleter):
         QCompleter.__init__(self)
         QCompleter.__init__(self)
         self.setCompletionMode(QCompleter.PopupCompletion)
         self.setCompletionMode(QCompleter.PopupCompletion)
         self.highlighted.connect(self.setHighlighted)
         self.highlighted.connect(self.setHighlighted)
+        # self.popup().installEventFilter(self)
+
+    # def eventFilter(self, obj, event):
+    #     if event.type() == QtCore.QEvent.Wheel and obj is self.popup():
+    #         pass
+    #     return False
 
 
     def setHighlighted(self, text):
     def setHighlighted(self, text):
         self.lastSelected = text
         self.lastSelected = text
 
 
     def getSelected(self):
     def getSelected(self):
         return self.lastSelected
         return self.lastSelected
+

+ 127 - 166
flatcamGUI/ObjectUI.py

@@ -76,7 +76,7 @@ class ObjectUI(QtWidgets.QWidget):
         # ###########################
         # ###########################
 
 
         # ### Scale ####
         # ### Scale ####
-        self.scale_label = QtWidgets.QLabel(_('<b>Scale:</b>'))
+        self.scale_label = QtWidgets.QLabel('<b>%s:</b>' % _('Scale'))
         self.scale_label.setToolTip(
         self.scale_label.setToolTip(
             _("Change the size of the object.")
             _("Change the size of the object.")
         )
         )
@@ -86,7 +86,7 @@ class ObjectUI(QtWidgets.QWidget):
         layout.addLayout(self.scale_grid)
         layout.addLayout(self.scale_grid)
 
 
         # Factor
         # Factor
-        faclabel = QtWidgets.QLabel(_('Factor:'))
+        faclabel = QtWidgets.QLabel('%s:' % _('Factor'))
         faclabel.setToolTip(
         faclabel.setToolTip(
             _("Factor by which to multiply\n"
             _("Factor by which to multiply\n"
               "geometric features of this object.")
               "geometric features of this object.")
@@ -105,7 +105,7 @@ class ObjectUI(QtWidgets.QWidget):
         self.scale_grid.addWidget(self.scale_button, 0, 2)
         self.scale_grid.addWidget(self.scale_button, 0, 2)
 
 
         # ### Offset ####
         # ### Offset ####
-        self.offset_label = QtWidgets.QLabel(_('<b>Offset:</b>'))
+        self.offset_label = QtWidgets.QLabel('<b>%s:</b>' % _('Offset'))
         self.offset_label.setToolTip(
         self.offset_label.setToolTip(
             _("Change the position of this object.")
             _("Change the position of this object.")
         )
         )
@@ -114,7 +114,7 @@ class ObjectUI(QtWidgets.QWidget):
         self.offset_grid = QtWidgets.QGridLayout()
         self.offset_grid = QtWidgets.QGridLayout()
         layout.addLayout(self.offset_grid)
         layout.addLayout(self.offset_grid)
 
 
-        self.offset_vectorlabel = QtWidgets.QLabel(_('Vector:'))
+        self.offset_vectorlabel = QtWidgets.QLabel('%s:' % _('Vector'))
         self.offset_vectorlabel.setToolTip(
         self.offset_vectorlabel.setToolTip(
             _("Amount by which to move the object\n"
             _("Amount by which to move the object\n"
               "in the x and y axes in (x, y) format.")
               "in the x and y axes in (x, y) format.")
@@ -147,7 +147,7 @@ class GerberObjectUI(ObjectUI):
         grid0.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
         grid0.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
         self.custom_box.addLayout(grid0)
         self.custom_box.addLayout(grid0)
 
 
-        self.plot_options_label = QtWidgets.QLabel(_("<b>Plot Options:</b>"))
+        self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
         self.plot_options_label.setMinimumWidth(90)
         self.plot_options_label.setMinimumWidth(90)
 
 
         grid0.addWidget(self.plot_options_label, 0, 0)
         grid0.addWidget(self.plot_options_label, 0, 0)
@@ -179,7 +179,7 @@ class GerberObjectUI(ObjectUI):
         # ## Object name
         # ## Object name
         self.name_hlay = QtWidgets.QHBoxLayout()
         self.name_hlay = QtWidgets.QHBoxLayout()
         self.custom_box.addLayout(self.name_hlay)
         self.custom_box.addLayout(self.name_hlay)
-        name_label = QtWidgets.QLabel(_("<b>Name:</b>"))
+        name_label = QtWidgets.QLabel("<b>%s:</b>" % _("Name"))
         self.name_entry = FCEntry()
         self.name_entry = FCEntry()
         self.name_entry.setFocusPolicy(QtCore.Qt.StrongFocus)
         self.name_entry.setFocusPolicy(QtCore.Qt.StrongFocus)
         self.name_hlay.addWidget(name_label)
         self.name_hlay.addWidget(name_label)
@@ -189,7 +189,7 @@ class GerberObjectUI(ObjectUI):
         self.custom_box.addLayout(hlay_plot)
         self.custom_box.addLayout(hlay_plot)
 
 
         # ### Gerber Apertures ####
         # ### Gerber Apertures ####
-        self.apertures_table_label = QtWidgets.QLabel(_('<b>Apertures:</b>'))
+        self.apertures_table_label = QtWidgets.QLabel('<b>%s:</b>' % _('Apertures'))
         self.apertures_table_label.setToolTip(
         self.apertures_table_label.setToolTip(
             _("Apertures Table for the Gerber Object.")
             _("Apertures Table for the Gerber Object.")
         )
         )
@@ -247,7 +247,7 @@ class GerberObjectUI(ObjectUI):
         self.apertures_table.setVisible(False)
         self.apertures_table.setVisible(False)
 
 
         # Isolation Routing
         # Isolation Routing
-        self.isolation_routing_label = QtWidgets.QLabel(_("<b>Isolation Routing:</b>"))
+        self.isolation_routing_label = QtWidgets.QLabel("<b>%s:</b>" % _("Isolation Routing"))
         self.isolation_routing_label.setToolTip(
         self.isolation_routing_label.setToolTip(
             _("Create a Geometry object with\n"
             _("Create a Geometry object with\n"
               "toolpaths to cut outside polygons.")
               "toolpaths to cut outside polygons.")
@@ -256,7 +256,7 @@ class GerberObjectUI(ObjectUI):
 
 
         grid1 = QtWidgets.QGridLayout()
         grid1 = QtWidgets.QGridLayout()
         self.custom_box.addLayout(grid1)
         self.custom_box.addLayout(grid1)
-        tdlabel = QtWidgets.QLabel(_('Tool dia:'))
+        tdlabel = QtWidgets.QLabel('%s:' % _('Tool dia'))
         tdlabel.setToolTip(
         tdlabel.setToolTip(
             _("Diameter of the cutting tool.\n"
             _("Diameter of the cutting tool.\n"
               "If you want to have an isolation path\n"
               "If you want to have an isolation path\n"
@@ -269,7 +269,7 @@ class GerberObjectUI(ObjectUI):
         self.iso_tool_dia_entry = LengthEntry()
         self.iso_tool_dia_entry = LengthEntry()
         grid1.addWidget(self.iso_tool_dia_entry, 0, 1)
         grid1.addWidget(self.iso_tool_dia_entry, 0, 1)
 
 
-        passlabel = QtWidgets.QLabel(_('Passes:'))
+        passlabel = QtWidgets.QLabel('%s:' % _('# Passes'))
         passlabel.setToolTip(
         passlabel.setToolTip(
             _("Width of the isolation gap in\n"
             _("Width of the isolation gap in\n"
               "number (integer) of tool widths.")
               "number (integer) of tool widths.")
@@ -280,7 +280,7 @@ class GerberObjectUI(ObjectUI):
         self.iso_width_entry.setRange(1, 999)
         self.iso_width_entry.setRange(1, 999)
         grid1.addWidget(self.iso_width_entry, 1, 1)
         grid1.addWidget(self.iso_width_entry, 1, 1)
 
 
-        overlabel = QtWidgets.QLabel(_('Pass overlap:'))
+        overlabel = QtWidgets.QLabel('%s:' % _('Pass overlap'))
         overlabel.setToolTip(
         overlabel.setToolTip(
             _("How much (fraction) of the tool width to overlap each tool pass.\n"
             _("How much (fraction) of the tool width to overlap each tool pass.\n"
               "Example:\n"
               "Example:\n"
@@ -292,7 +292,7 @@ class GerberObjectUI(ObjectUI):
         grid1.addWidget(self.iso_overlap_entry, 2, 1)
         grid1.addWidget(self.iso_overlap_entry, 2, 1)
 
 
         # Milling Type Radio Button
         # Milling Type Radio Button
-        self.milling_type_label = QtWidgets.QLabel(_('Milling Type:'))
+        self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
         self.milling_type_label.setToolTip(
         self.milling_type_label.setToolTip(
             _("Milling type:\n"
             _("Milling type:\n"
               "- climb / best for precision milling and to reduce tool usage\n"
               "- climb / best for precision milling and to reduce tool usage\n"
@@ -304,7 +304,7 @@ class GerberObjectUI(ObjectUI):
         grid1.addWidget(self.milling_type_radio, 3, 1)
         grid1.addWidget(self.milling_type_radio, 3, 1)
 
 
         # combine all passes CB
         # combine all passes CB
-        self.combine_passes_cb = FCCheckBox(label=_('Combine'))
+        self.combine_passes_cb = FCCheckBox(label=_('Combine Passes'))
         self.combine_passes_cb.setToolTip(
         self.combine_passes_cb.setToolTip(
             _("Combine all passes into one object")
             _("Combine all passes into one object")
         )
         )
@@ -320,7 +320,7 @@ class GerberObjectUI(ObjectUI):
         )
         )
         grid1.addWidget(self.follow_cb, 4, 1)
         grid1.addWidget(self.follow_cb, 4, 1)
 
 
-        self.gen_iso_label = QtWidgets.QLabel(_("<b>Generate Isolation Geometry:</b>"))
+        self.gen_iso_label = QtWidgets.QLabel("<b>%s:</b>" % _("Generate Isolation Geometry"))
         self.gen_iso_label.setToolTip(
         self.gen_iso_label.setToolTip(
             _("Create a Geometry object with toolpaths to cut \n"
             _("Create a Geometry object with toolpaths to cut \n"
               "isolation outside, inside or on both sides of the\n"
               "isolation outside, inside or on both sides of the\n"
@@ -379,7 +379,7 @@ class GerberObjectUI(ObjectUI):
         self.custom_box.addLayout(grid2)
         self.custom_box.addLayout(grid2)
 
 
         # ## Clear non-copper regions
         # ## Clear non-copper regions
-        self.clearcopper_label = QtWidgets.QLabel(_("<b>Clear N-copper:</b>"))
+        self.clearcopper_label = QtWidgets.QLabel("<b>%s:</b>" % _("Clear N-copper"))
         self.clearcopper_label.setToolTip(
         self.clearcopper_label.setToolTip(
             _("Create a Geometry object with\n"
             _("Create a Geometry object with\n"
               "toolpaths to cut all non-copper regions.")
               "toolpaths to cut all non-copper regions.")
@@ -395,7 +395,7 @@ class GerberObjectUI(ObjectUI):
         grid2.addWidget(self.generate_ncc_button, 0, 1)
         grid2.addWidget(self.generate_ncc_button, 0, 1)
 
 
         # ## Board cutout
         # ## Board cutout
-        self.board_cutout_label = QtWidgets.QLabel(_("<b>Board cutout:</b>"))
+        self.board_cutout_label = QtWidgets.QLabel("<b>%s:</b>" % _("Board cutout"))
         self.board_cutout_label.setToolTip(
         self.board_cutout_label.setToolTip(
             _("Create toolpaths to cut around\n"
             _("Create toolpaths to cut around\n"
               "the PCB and separate it from\n"
               "the PCB and separate it from\n"
@@ -411,7 +411,7 @@ class GerberObjectUI(ObjectUI):
         grid2.addWidget(self.generate_cutout_button, 1, 1)
         grid2.addWidget(self.generate_cutout_button, 1, 1)
 
 
         # ## Non-copper regions
         # ## Non-copper regions
-        self.noncopper_label = QtWidgets.QLabel(_("<b>Non-copper regions:</b>"))
+        self.noncopper_label = QtWidgets.QLabel("<b>%s:</b>" % _("Non-copper regions"))
         self.noncopper_label.setToolTip(
         self.noncopper_label.setToolTip(
             _("Create polygons covering the\n"
             _("Create polygons covering the\n"
               "areas without copper on the PCB.\n"
               "areas without copper on the PCB.\n"
@@ -425,7 +425,7 @@ class GerberObjectUI(ObjectUI):
         self.custom_box.addLayout(grid4)
         self.custom_box.addLayout(grid4)
 
 
         # Margin
         # Margin
-        bmlabel = QtWidgets.QLabel(_('Boundary Margin:'))
+        bmlabel = QtWidgets.QLabel('%s:' % _('Boundary Margin'))
         bmlabel.setToolTip(
         bmlabel.setToolTip(
             _("Specify the edge of the PCB\n"
             _("Specify the edge of the PCB\n"
               "by drawing a box around all\n"
               "by drawing a box around all\n"
@@ -449,7 +449,7 @@ class GerberObjectUI(ObjectUI):
         grid4.addWidget(self.generate_noncopper_button, 1, 1)
         grid4.addWidget(self.generate_noncopper_button, 1, 1)
 
 
         # ## Bounding box
         # ## Bounding box
-        self.boundingbox_label = QtWidgets.QLabel(_('<b>Bounding Box:</b>'))
+        self.boundingbox_label = QtWidgets.QLabel('<b>%s:</b>' % _('Bounding Box'))
         self.boundingbox_label.setToolTip(
         self.boundingbox_label.setToolTip(
             _("Create a geometry surrounding the Gerber object.\n"
             _("Create a geometry surrounding the Gerber object.\n"
               "Square shape.")
               "Square shape.")
@@ -459,7 +459,7 @@ class GerberObjectUI(ObjectUI):
         grid5 = QtWidgets.QGridLayout()
         grid5 = QtWidgets.QGridLayout()
         self.custom_box.addLayout(grid5)
         self.custom_box.addLayout(grid5)
 
 
-        bbmargin = QtWidgets.QLabel(_('Boundary Margin:'))
+        bbmargin = QtWidgets.QLabel('%s:' % _('Boundary Margin'))
         bbmargin.setToolTip(
         bbmargin.setToolTip(
             _("Distance of the edges of the box\n"
             _("Distance of the edges of the box\n"
               "to the nearest polygon.")
               "to the nearest polygon.")
@@ -500,7 +500,7 @@ class ExcellonObjectUI(ObjectUI):
         hlay_plot = QtWidgets.QHBoxLayout()
         hlay_plot = QtWidgets.QHBoxLayout()
         self.custom_box.addLayout(hlay_plot)
         self.custom_box.addLayout(hlay_plot)
 
 
-        self.plot_options_label = QtWidgets.QLabel(_("<b>Plot Options:</b>"))
+        self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
         self.solid_cb = FCCheckBox(label=_('Solid'))
         self.solid_cb = FCCheckBox(label=_('Solid'))
         self.solid_cb.setToolTip(
         self.solid_cb.setToolTip(
             _("Solid circles.")
             _("Solid circles.")
@@ -512,7 +512,7 @@ class ExcellonObjectUI(ObjectUI):
         # ## Object name
         # ## Object name
         self.name_hlay = QtWidgets.QHBoxLayout()
         self.name_hlay = QtWidgets.QHBoxLayout()
         self.custom_box.addLayout(self.name_hlay)
         self.custom_box.addLayout(self.name_hlay)
-        name_label = QtWidgets.QLabel(_("<b>Name:</b>"))
+        name_label = QtWidgets.QLabel("<b>%s:</b>" % _("Name"))
         self.name_entry = FCEntry()
         self.name_entry = FCEntry()
         self.name_entry.setFocusPolicy(QtCore.Qt.StrongFocus)
         self.name_entry.setFocusPolicy(QtCore.Qt.StrongFocus)
         self.name_hlay.addWidget(name_label)
         self.name_hlay.addWidget(name_label)
@@ -531,7 +531,7 @@ class ExcellonObjectUI(ObjectUI):
         self.tools_box.addLayout(hlay_plot)
         self.tools_box.addLayout(hlay_plot)
 
 
         # ### Tools Drills ####
         # ### Tools Drills ####
-        self.tools_table_label = QtWidgets.QLabel(_('<b>Tools Table</b>'))
+        self.tools_table_label = QtWidgets.QLabel('<b>%s:</b>' % _('Tools Table'))
         self.tools_table_label.setToolTip(
         self.tools_table_label.setToolTip(
             _("Tools in this Excellon object\n"
             _("Tools in this Excellon object\n"
               "when are used for drilling.")
               "when are used for drilling.")
@@ -539,7 +539,7 @@ class ExcellonObjectUI(ObjectUI):
         hlay_plot.addWidget(self.tools_table_label)
         hlay_plot.addWidget(self.tools_table_label)
 
 
         # Plot CB
         # Plot CB
-        self.plot_cb = FCCheckBox(_('Plot Object'))
+        self.plot_cb = FCCheckBox(_('Plot'))
         self.plot_cb.setToolTip(
         self.plot_cb.setToolTip(
             _("Plot (show) this object.")
             _("Plot (show) this object.")
         )
         )
@@ -579,7 +579,7 @@ class ExcellonObjectUI(ObjectUI):
         self.tools_box.addWidget(self.empty_label)
         self.tools_box.addWidget(self.empty_label)
 
 
         # ### Create CNC Job ####
         # ### Create CNC Job ####
-        self.cncjob_label = QtWidgets.QLabel(_('<b>Create CNC Job</b>'))
+        self.cncjob_label = QtWidgets.QLabel('<b>%s</b>' % _('Create CNC Job'))
         self.cncjob_label.setToolTip(
         self.cncjob_label.setToolTip(
             _("Create a CNC Job object\n"
             _("Create a CNC Job object\n"
               "for this drill object.")
               "for this drill object.")
@@ -590,7 +590,7 @@ class ExcellonObjectUI(ObjectUI):
         self.tools_box.addLayout(grid1)
         self.tools_box.addLayout(grid1)
 
 
         # Cut Z
         # Cut Z
-        cutzlabel = QtWidgets.QLabel(_('Cut Z:'))
+        cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
         cutzlabel.setToolTip(
         cutzlabel.setToolTip(
             _("Drill depth (negative)\n"
             _("Drill depth (negative)\n"
               "below the copper surface.")
               "below the copper surface.")
@@ -600,7 +600,7 @@ class ExcellonObjectUI(ObjectUI):
         grid1.addWidget(self.cutz_entry, 0, 1)
         grid1.addWidget(self.cutz_entry, 0, 1)
 
 
         # Travel Z (z_move)
         # Travel Z (z_move)
-        travelzlabel = QtWidgets.QLabel(_('Travel Z:'))
+        travelzlabel = QtWidgets.QLabel('%s:' % _('Travel Z'))
         travelzlabel.setToolTip(
         travelzlabel.setToolTip(
             _("Tool height when travelling\n"
             _("Tool height when travelling\n"
               "across the XY plane.")
               "across the XY plane.")
@@ -610,7 +610,7 @@ class ExcellonObjectUI(ObjectUI):
         grid1.addWidget(self.travelz_entry, 1, 1)
         grid1.addWidget(self.travelz_entry, 1, 1)
 
 
         # Tool change:
         # Tool change:
-        self.toolchange_cb = FCCheckBox(_("Tool change"))
+        self.toolchange_cb = FCCheckBox('%s:' % _("Tool change"))
         self.toolchange_cb.setToolTip(
         self.toolchange_cb.setToolTip(
             _("Include tool-change sequence\n"
             _("Include tool-change sequence\n"
               "in G-Code (Pause for tool change).")
               "in G-Code (Pause for tool change).")
@@ -618,7 +618,7 @@ class ExcellonObjectUI(ObjectUI):
         grid1.addWidget(self.toolchange_cb, 2, 0)
         grid1.addWidget(self.toolchange_cb, 2, 0)
 
 
         # Tool change Z:
         # Tool change Z:
-        toolchzlabel = QtWidgets.QLabel(_("Tool change Z:"))
+        toolchzlabel = QtWidgets.QLabel('%s:' % _("Tool change Z"))
         toolchzlabel.setToolTip(
         toolchzlabel.setToolTip(
             _("Z-axis position (height) for\n"
             _("Z-axis position (height) for\n"
               "tool change.")
               "tool change.")
@@ -629,9 +629,9 @@ class ExcellonObjectUI(ObjectUI):
         self.ois_tcz_e = OptionalInputSection(self.toolchange_cb, [self.toolchangez_entry])
         self.ois_tcz_e = OptionalInputSection(self.toolchange_cb, [self.toolchangez_entry])
 
 
         # Start move Z:
         # Start move Z:
-        self.estartz_label = QtWidgets.QLabel(_("Start move Z:"))
+        self.estartz_label = QtWidgets.QLabel('%s:' % _("Start move Z"))
         self.estartz_label.setToolTip(
         self.estartz_label.setToolTip(
-            _("Tool height just before starting the work.\n"
+            _("Height of the tool just after start.\n"
               "Delete the value if you don't need this feature.")
               "Delete the value if you don't need this feature.")
         )
         )
         grid1.addWidget(self.estartz_label, 4, 0)
         grid1.addWidget(self.estartz_label, 4, 0)
@@ -639,17 +639,17 @@ class ExcellonObjectUI(ObjectUI):
         grid1.addWidget(self.estartz_entry, 4, 1)
         grid1.addWidget(self.estartz_entry, 4, 1)
 
 
         # End move Z:
         # End move Z:
-        self.eendz_label = QtWidgets.QLabel(_("End move Z:"))
+        self.eendz_label = QtWidgets.QLabel('%s:' % _("End move Z"))
         self.eendz_label.setToolTip(
         self.eendz_label.setToolTip(
-            _("Z-axis position (height) for\n"
-              "the last move.")
+            _("Height of the tool after\n"
+              "the last move at the end of the job.")
         )
         )
         grid1.addWidget(self.eendz_label, 5, 0)
         grid1.addWidget(self.eendz_label, 5, 0)
         self.eendz_entry = LengthEntry()
         self.eendz_entry = LengthEntry()
         grid1.addWidget(self.eendz_entry, 5, 1)
         grid1.addWidget(self.eendz_entry, 5, 1)
 
 
         # Excellon Feedrate
         # Excellon Feedrate
-        frlabel = QtWidgets.QLabel(_('Feedrate (Plunge):'))
+        frlabel = QtWidgets.QLabel('%s:' % _('Feedrate (Plunge):'))
         frlabel.setToolTip(
         frlabel.setToolTip(
             _("Tool speed while drilling\n"
             _("Tool speed while drilling\n"
               "(in units per minute).\n"
               "(in units per minute).\n"
@@ -660,14 +660,13 @@ class ExcellonObjectUI(ObjectUI):
         grid1.addWidget(self.feedrate_entry, 6, 1)
         grid1.addWidget(self.feedrate_entry, 6, 1)
 
 
         # Excellon Rapid Feedrate
         # Excellon Rapid Feedrate
-        self.feedrate_rapid_label = QtWidgets.QLabel(_('Feedrate Rapids:'))
+        self.feedrate_rapid_label = QtWidgets.QLabel('%s:' % _('Feedrate Rapids'))
         self.feedrate_rapid_label.setToolTip(
         self.feedrate_rapid_label.setToolTip(
             _("Tool speed while drilling\n"
             _("Tool speed while drilling\n"
               "(in units per minute).\n"
               "(in units per minute).\n"
               "This is for the rapid move G00.\n"
               "This is for the rapid move G00.\n"
               "It is useful only for Marlin,\n"
               "It is useful only for Marlin,\n"
-              "ignore for any other cases."
-              )
+              "ignore for any other cases.")
         )
         )
         grid1.addWidget(self.feedrate_rapid_label, 7, 0)
         grid1.addWidget(self.feedrate_rapid_label, 7, 0)
         self.feedrate_rapid_entry = LengthEntry()
         self.feedrate_rapid_entry = LengthEntry()
@@ -677,7 +676,7 @@ class ExcellonObjectUI(ObjectUI):
         self.feedrate_rapid_entry.hide()
         self.feedrate_rapid_entry.hide()
 
 
         # Spindlespeed
         # Spindlespeed
-        spdlabel = QtWidgets.QLabel(_('Spindle speed:'))
+        spdlabel = QtWidgets.QLabel('%s:' % _('Spindle speed'))
         spdlabel.setToolTip(
         spdlabel.setToolTip(
             _("Speed of the spindle\n"
             _("Speed of the spindle\n"
               "in RPM (optional)")
               "in RPM (optional)")
@@ -687,14 +686,14 @@ class ExcellonObjectUI(ObjectUI):
         grid1.addWidget(self.spindlespeed_entry, 8, 1)
         grid1.addWidget(self.spindlespeed_entry, 8, 1)
 
 
         # Dwell
         # Dwell
-        self.dwell_cb = FCCheckBox(_('Dwell:'))
+        self.dwell_cb = FCCheckBox('%s:' % _('Dwell'))
         self.dwell_cb.setToolTip(
         self.dwell_cb.setToolTip(
             _("Pause to allow the spindle to reach its\n"
             _("Pause to allow the spindle to reach its\n"
               "speed before cutting.")
               "speed before cutting.")
         )
         )
         self.dwelltime_entry = FCEntry()
         self.dwelltime_entry = FCEntry()
         self.dwelltime_entry.setToolTip(
         self.dwelltime_entry.setToolTip(
-            _("Number of milliseconds for spindle to dwell.")
+            _("Number of time units for spindle to dwell.")
         )
         )
         grid1.addWidget(self.dwell_cb, 9, 0)
         grid1.addWidget(self.dwell_cb, 9, 0)
         grid1.addWidget(self.dwelltime_entry, 9, 1)
         grid1.addWidget(self.dwelltime_entry, 9, 1)
@@ -702,10 +701,10 @@ class ExcellonObjectUI(ObjectUI):
         self.ois_dwell = OptionalInputSection(self.dwell_cb, [self.dwelltime_entry])
         self.ois_dwell = OptionalInputSection(self.dwell_cb, [self.dwelltime_entry])
 
 
         # postprocessor selection
         # postprocessor selection
-        pp_excellon_label = QtWidgets.QLabel(_("Postprocessor:"))
+        pp_excellon_label = QtWidgets.QLabel('%s:' % _("Postprocessor"))
         pp_excellon_label.setToolTip(
         pp_excellon_label.setToolTip(
-            _("The json file that dictates\n"
-              "gcode output.")
+            _("The postprocessor JSON file that dictates\n"
+              "Gcode output.")
         )
         )
         self.pp_excellon_name_cb = FCComboBox()
         self.pp_excellon_name_cb = FCComboBox()
         self.pp_excellon_name_cb.setFocusPolicy(QtCore.Qt.StrongFocus)
         self.pp_excellon_name_cb.setFocusPolicy(QtCore.Qt.StrongFocus)
@@ -713,7 +712,7 @@ class ExcellonObjectUI(ObjectUI):
         grid1.addWidget(self.pp_excellon_name_cb, 10, 1)
         grid1.addWidget(self.pp_excellon_name_cb, 10, 1)
 
 
         # Probe depth
         # Probe depth
-        self.pdepth_label = QtWidgets.QLabel(_("Probe Z depth:"))
+        self.pdepth_label = QtWidgets.QLabel('%s:' % _("Probe Z depth"))
         self.pdepth_label.setToolTip(
         self.pdepth_label.setToolTip(
             _("The maximum depth that the probe is allowed\n"
             _("The maximum depth that the probe is allowed\n"
               "to probe. Negative value, in current units.")
               "to probe. Negative value, in current units.")
@@ -725,7 +724,7 @@ class ExcellonObjectUI(ObjectUI):
         self.pdepth_entry.setVisible(False)
         self.pdepth_entry.setVisible(False)
 
 
         # Probe feedrate
         # Probe feedrate
-        self.feedrate_probe_label = QtWidgets.QLabel(_("Feedrate Probe:"))
+        self.feedrate_probe_label = QtWidgets.QLabel('%s:' % _("Feedrate Probe"))
         self.feedrate_probe_label.setToolTip(
         self.feedrate_probe_label.setToolTip(
             _("The feedrate used while the probe is probing.")
             _("The feedrate used while the probe is probing.")
         )
         )
@@ -743,7 +742,7 @@ class ExcellonObjectUI(ObjectUI):
 
 
         # ### Choose what to use for Gcode creation: Drills, Slots or Both
         # ### Choose what to use for Gcode creation: Drills, Slots or Both
         gcode_box = QtWidgets.QFormLayout()
         gcode_box = QtWidgets.QFormLayout()
-        gcode_type_label = QtWidgets.QLabel(_('<b>Type:    </b>'))
+        gcode_type_label = QtWidgets.QLabel('<b>%s</b>' % _('Gcode'))
         gcode_type_label.setToolTip(
         gcode_type_label.setToolTip(
             _("Choose what to use for GCode generation:\n"
             _("Choose what to use for GCode generation:\n"
               "'Drills', 'Slots' or 'Both'.\n"
               "'Drills', 'Slots' or 'Both'.\n"
@@ -767,7 +766,7 @@ class ExcellonObjectUI(ObjectUI):
         self.tools_box.addWidget(self.generate_cnc_button)
         self.tools_box.addWidget(self.generate_cnc_button)
 
 
         # ### Milling Holes Drills ####
         # ### Milling Holes Drills ####
-        self.mill_hole_label = QtWidgets.QLabel(_('<b>Mill Holes</b>'))
+        self.mill_hole_label = QtWidgets.QLabel('<b>%s</b>' % _('Mill Holes'))
         self.mill_hole_label.setToolTip(
         self.mill_hole_label.setToolTip(
             _("Create Geometry for milling holes.")
             _("Create Geometry for milling holes.")
         )
         )
@@ -781,7 +780,7 @@ class ExcellonObjectUI(ObjectUI):
 
 
         grid2 = QtWidgets.QGridLayout()
         grid2 = QtWidgets.QGridLayout()
         self.tools_box.addLayout(grid2)
         self.tools_box.addLayout(grid2)
-        self.tdlabel = QtWidgets.QLabel(_('Drills Tool dia:'))
+        self.tdlabel = QtWidgets.QLabel('%s:' % _('Drill Tool dia'))
         self.tdlabel.setToolTip(
         self.tdlabel.setToolTip(
             _("Diameter of the cutting tool.")
             _("Diameter of the cutting tool.")
         )
         )
@@ -797,9 +796,10 @@ class ExcellonObjectUI(ObjectUI):
 
 
         grid3 = QtWidgets.QGridLayout()
         grid3 = QtWidgets.QGridLayout()
         self.custom_box.addLayout(grid3)
         self.custom_box.addLayout(grid3)
-        self.stdlabel = QtWidgets.QLabel(_('Slots Tool dia:'))
+        self.stdlabel = QtWidgets.QLabel('%s:' % _('Slot Tool dia'))
         self.stdlabel.setToolTip(
         self.stdlabel.setToolTip(
-            _("Diameter of the cutting tool.")
+            _("Diameter of the cutting tool\n"
+              "when milling slots.")
         )
         )
         grid3.addWidget(self.stdlabel, 0, 0)
         grid3.addWidget(self.stdlabel, 0, 0)
         self.slot_tooldia_entry = LengthEntry()
         self.slot_tooldia_entry = LengthEntry()
@@ -828,13 +828,13 @@ class GeometryObjectUI(ObjectUI):
                                                icon_file='share/geometry32.png', parent=parent)
                                                icon_file='share/geometry32.png', parent=parent)
 
 
         # Plot options
         # Plot options
-        self.plot_options_label = QtWidgets.QLabel(_("<b>Plot Options:</b>"))
+        self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
         self.custom_box.addWidget(self.plot_options_label)
         self.custom_box.addWidget(self.plot_options_label)
 
 
         # ## Object name
         # ## Object name
         self.name_hlay = QtWidgets.QHBoxLayout()
         self.name_hlay = QtWidgets.QHBoxLayout()
         self.custom_box.addLayout(self.name_hlay)
         self.custom_box.addLayout(self.name_hlay)
-        name_label = QtWidgets.QLabel(_("<b>Name:</b>"))
+        name_label = QtWidgets.QLabel("<b>%s:</b>" % _("Name"))
         self.name_entry = FCEntry()
         self.name_entry = FCEntry()
         self.name_entry.setFocusPolicy(QtCore.Qt.StrongFocus)
         self.name_entry.setFocusPolicy(QtCore.Qt.StrongFocus)
         self.name_hlay.addWidget(name_label)
         self.name_hlay.addWidget(name_label)
@@ -853,7 +853,7 @@ class GeometryObjectUI(ObjectUI):
         self.geo_tools_box.addLayout(hlay_plot)
         self.geo_tools_box.addLayout(hlay_plot)
 
 
         # ### Tools ####
         # ### Tools ####
-        self.tools_table_label = QtWidgets.QLabel(_('<b>Tools Table</b>'))
+        self.tools_table_label = QtWidgets.QLabel('<b>%s:</b>' % _('Tools Table'))
         self.tools_table_label.setToolTip(
         self.tools_table_label.setToolTip(
             _("Tools in this Geometry object used for cutting.\n"
             _("Tools in this Geometry object used for cutting.\n"
               "The 'Offset' entry will set an offset for the cut.\n"
               "The 'Offset' entry will set an offset for the cut.\n"
@@ -945,7 +945,7 @@ class GeometryObjectUI(ObjectUI):
         self.grid1 = QtWidgets.QGridLayout()
         self.grid1 = QtWidgets.QGridLayout()
         self.geo_tools_box.addLayout(self.grid1)
         self.geo_tools_box.addLayout(self.grid1)
 
 
-        self.tool_offset_lbl = QtWidgets.QLabel(_('Tool Offset:'))
+        self.tool_offset_lbl = QtWidgets.QLabel('%s:' % _('Tool Offset'))
         self.tool_offset_lbl.setToolTip(
         self.tool_offset_lbl.setToolTip(
             _(
             _(
                 "The value to offset the cut when \n"
                 "The value to offset the cut when \n"
@@ -971,7 +971,7 @@ class GeometryObjectUI(ObjectUI):
         # self.addtool_label.setToolTip(
         # self.addtool_label.setToolTip(
         #     "Add/Copy/Delete a tool to the tool list."
         #     "Add/Copy/Delete a tool to the tool list."
         # )
         # )
-        self.addtool_entry_lbl = QtWidgets.QLabel(_('<b>Tool Dia:</b>'))
+        self.addtool_entry_lbl = QtWidgets.QLabel('<b>%s:</b>' % _('Tool Dia'))
         self.addtool_entry_lbl.setToolTip(
         self.addtool_entry_lbl.setToolTip(
             _(
             _(
                 "Diameter for the new tool"
                 "Diameter for the new tool"
@@ -1022,7 +1022,7 @@ class GeometryObjectUI(ObjectUI):
         # Create CNC Job ###
         # Create CNC Job ###
         # ##################
         # ##################
         # ### Tools Data ## ##
         # ### Tools Data ## ##
-        self.tool_data_label = QtWidgets.QLabel(_('<b>Tool Data</b>'))
+        self.tool_data_label = QtWidgets.QLabel('<b>%s</b>' % _('Tool Data'))
         self.tool_data_label.setToolTip(
         self.tool_data_label.setToolTip(
             _(
             _(
                 "The data used for creating GCode.\n"
                 "The data used for creating GCode.\n"
@@ -1043,7 +1043,7 @@ class GeometryObjectUI(ObjectUI):
         self.geo_param_box.addLayout(self.grid3)
         self.geo_param_box.addLayout(self.grid3)
 
 
         # Tip Dia
         # Tip Dia
-        self.tipdialabel = QtWidgets.QLabel(_('V-Tip Dia:'))
+        self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
         self.tipdialabel.setToolTip(
         self.tipdialabel.setToolTip(
             _(
             _(
                 "The tip diameter for V-Shape Tool"
                 "The tip diameter for V-Shape Tool"
@@ -1054,7 +1054,7 @@ class GeometryObjectUI(ObjectUI):
         self.grid3.addWidget(self.tipdia_entry, 1, 1)
         self.grid3.addWidget(self.tipdia_entry, 1, 1)
 
 
         # Tip Angle
         # Tip Angle
-        self.tipanglelabel = QtWidgets.QLabel(_('V-Tip Angle:'))
+        self.tipanglelabel = QtWidgets.QLabel('%s:' % _('V-Tip Angle'))
         self.tipanglelabel.setToolTip(
         self.tipanglelabel.setToolTip(
             _(
             _(
                 "The tip angle for V-Shape Tool.\n"
                 "The tip angle for V-Shape Tool.\n"
@@ -1066,7 +1066,7 @@ class GeometryObjectUI(ObjectUI):
         self.grid3.addWidget(self.tipangle_entry, 2, 1)
         self.grid3.addWidget(self.tipangle_entry, 2, 1)
 
 
         # Cut Z
         # Cut Z
-        cutzlabel = QtWidgets.QLabel(_('Cut Z:'))
+        cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
         cutzlabel.setToolTip(
         cutzlabel.setToolTip(
             _(
             _(
                 "Cutting depth (negative)\n"
                 "Cutting depth (negative)\n"
@@ -1078,15 +1078,13 @@ class GeometryObjectUI(ObjectUI):
         self.grid3.addWidget(self.cutz_entry, 3, 1)
         self.grid3.addWidget(self.cutz_entry, 3, 1)
 
 
         # Multi-pass
         # Multi-pass
-        self.mpass_cb = FCCheckBox(_("Multi-Depth:"))
+        self.mpass_cb = FCCheckBox('%s:' % _("Multi-Depth"))
         self.mpass_cb.setToolTip(
         self.mpass_cb.setToolTip(
             _(
             _(
                 "Use multiple passes to limit\n"
                 "Use multiple passes to limit\n"
                 "the cut depth in each pass. Will\n"
                 "the cut depth in each pass. Will\n"
                 "cut multiple times until Cut Z is\n"
                 "cut multiple times until Cut Z is\n"
-                "reached.\n"
-                "To the right, input the depth of \n"
-                "each pass (positive value)."
+                "reached."
             )
             )
         )
         )
         self.grid3.addWidget(self.mpass_cb, 4, 0)
         self.grid3.addWidget(self.mpass_cb, 4, 0)
@@ -1102,12 +1100,10 @@ class GeometryObjectUI(ObjectUI):
         self.ois_mpass_geo = OptionalInputSection(self.mpass_cb, [self.maxdepth_entry])
         self.ois_mpass_geo = OptionalInputSection(self.mpass_cb, [self.maxdepth_entry])
 
 
         # Travel Z
         # Travel Z
-        travelzlabel = QtWidgets.QLabel(_('Travel Z:'))
+        travelzlabel = QtWidgets.QLabel('%s:' % _('Travel Z'))
         travelzlabel.setToolTip(
         travelzlabel.setToolTip(
-            _(
-                "Height of the tool when\n"
-                "moving without cutting."
-            )
+            _("Height of the tool when\n"
+              "moving without cutting.")
         )
         )
         self.grid3.addWidget(travelzlabel, 5, 0)
         self.grid3.addWidget(travelzlabel, 5, 0)
         self.travelz_entry = FloatEntry()
         self.travelz_entry = FloatEntry()
@@ -1115,14 +1111,14 @@ class GeometryObjectUI(ObjectUI):
 
 
         # Tool change:
         # Tool change:
 
 
-        self.toolchzlabel = QtWidgets.QLabel(_("Tool change Z:"))
+        self.toolchzlabel = QtWidgets.QLabel('%s:' %_("Tool change Z"))
         self.toolchzlabel.setToolTip(
         self.toolchzlabel.setToolTip(
             _(
             _(
                 "Z-axis position (height) for\n"
                 "Z-axis position (height) for\n"
                 "tool change."
                 "tool change."
             )
             )
         )
         )
-        self.toolchangeg_cb = FCCheckBox(_("Tool change"))
+        self.toolchangeg_cb = FCCheckBox('%s:' % _("Tool change"))
         self.toolchangeg_cb.setToolTip(
         self.toolchangeg_cb.setToolTip(
             _(
             _(
                 "Include tool-change sequence\n"
                 "Include tool-change sequence\n"
@@ -1148,52 +1144,44 @@ class GeometryObjectUI(ObjectUI):
         # self.grid3.addWidget(self.gstartz_entry, 8, 1)
         # self.grid3.addWidget(self.gstartz_entry, 8, 1)
 
 
         # The Z value for the end move
         # The Z value for the end move
-        self.endzlabel = QtWidgets.QLabel(_('End move Z:'))
+        self.endzlabel = QtWidgets.QLabel('%s:' % _('End move Z'))
         self.endzlabel.setToolTip(
         self.endzlabel.setToolTip(
-            _(
-                "This is the height (Z) at which the CNC\n"
-                "will go as the last move."
-            )
+            _("Height of the tool after\n"
+              "the last move at the end of the job.")
         )
         )
         self.grid3.addWidget(self.endzlabel, 9, 0)
         self.grid3.addWidget(self.endzlabel, 9, 0)
         self.gendz_entry = FloatEntry()
         self.gendz_entry = FloatEntry()
         self.grid3.addWidget(self.gendz_entry, 9, 1)
         self.grid3.addWidget(self.gendz_entry, 9, 1)
 
 
         # Feedrate X-Y
         # Feedrate X-Y
-        frlabel = QtWidgets.QLabel(_('Feed Rate X-Y:'))
+        frlabel = QtWidgets.QLabel('%s:' % _('Feed Rate X-Y'))
         frlabel.setToolTip(
         frlabel.setToolTip(
-            _(
-                "Cutting speed in the XY\n"
-                "plane in units per minute"
-            )
+            _("Cutting speed in the XY\n"
+              "plane in units per minute")
         )
         )
         self.grid3.addWidget(frlabel, 10, 0)
         self.grid3.addWidget(frlabel, 10, 0)
         self.cncfeedrate_entry = FloatEntry()
         self.cncfeedrate_entry = FloatEntry()
         self.grid3.addWidget(self.cncfeedrate_entry, 10, 1)
         self.grid3.addWidget(self.cncfeedrate_entry, 10, 1)
 
 
         # Feedrate Z (Plunge)
         # Feedrate Z (Plunge)
-        frzlabel = QtWidgets.QLabel(_('Feed Rate Z (Plunge):'))
+        frzlabel = QtWidgets.QLabel('%s:' % _('Feed Rate Z'))
         frzlabel.setToolTip(
         frzlabel.setToolTip(
-            _(
-                "Cutting speed in the Z\n"
-                "plane in units per minute"
-            )
+            _("Cutting speed in the XY\n"
+              "plane in units per minute.\n"
+              "It is called also Plunge.")
         )
         )
         self.grid3.addWidget(frzlabel, 11, 0)
         self.grid3.addWidget(frzlabel, 11, 0)
         self.cncplunge_entry = FloatEntry()
         self.cncplunge_entry = FloatEntry()
         self.grid3.addWidget(self.cncplunge_entry, 11, 1)
         self.grid3.addWidget(self.cncplunge_entry, 11, 1)
 
 
         # Feedrate rapids
         # Feedrate rapids
-        self.fr_rapidlabel = QtWidgets.QLabel(_('Feed Rate Rapids:'))
+        self.fr_rapidlabel = QtWidgets.QLabel('%s:' % _('Feed Rate Rapids'))
         self.fr_rapidlabel.setToolTip(
         self.fr_rapidlabel.setToolTip(
-            _(
-              "Cutting speed in the XY\n"
-              "plane in units per minute\n"
+            _("Cutting speed in the XY plane\n"
               "(in units per minute).\n"
               "(in units per minute).\n"
               "This is for the rapid move G00.\n"
               "This is for the rapid move G00.\n"
               "It is useful only for Marlin,\n"
               "It is useful only for Marlin,\n"
-              "ignore for any other cases."
-            )
+              "ignore for any other cases.")
         )
         )
         self.grid3.addWidget(self.fr_rapidlabel, 12, 0)
         self.grid3.addWidget(self.fr_rapidlabel, 12, 0)
         self.cncfeedrate_rapid_entry = FloatEntry()
         self.cncfeedrate_rapid_entry = FloatEntry()
@@ -1203,19 +1191,17 @@ class GeometryObjectUI(ObjectUI):
         self.cncfeedrate_rapid_entry.hide()
         self.cncfeedrate_rapid_entry.hide()
 
 
         # Cut over 1st point in path
         # Cut over 1st point in path
-        self.extracut_cb = FCCheckBox(_('Cut over 1st pt'))
+        self.extracut_cb = FCCheckBox('%s' % _('Re-cut 1st pt.'))
         self.extracut_cb.setToolTip(
         self.extracut_cb.setToolTip(
-            _(
-                "In order to remove possible\n"
-                "copper leftovers where first cut\n"
-                "meet with last cut, we generate an\n"
-                "extended cut over the first cut section."
-            )
+            _("In order to remove possible\n"
+              "copper leftovers where first cut\n"
+              "meet with last cut, we generate an\n"
+              "extended cut over the first cut section.")
         )
         )
         self.grid3.addWidget(self.extracut_cb, 13, 0)
         self.grid3.addWidget(self.extracut_cb, 13, 0)
 
 
         # Spindlespeed
         # Spindlespeed
-        spdlabel = QtWidgets.QLabel(_('Spindle speed:'))
+        spdlabel = QtWidgets.QLabel('%s:' % _('Spindle speed'))
         spdlabel.setToolTip(
         spdlabel.setToolTip(
             _(
             _(
                 "Speed of the spindle in RPM (optional).\n"
                 "Speed of the spindle in RPM (optional).\n"
@@ -1228,7 +1214,7 @@ class GeometryObjectUI(ObjectUI):
         self.grid3.addWidget(self.cncspindlespeed_entry, 14, 1)
         self.grid3.addWidget(self.cncspindlespeed_entry, 14, 1)
 
 
         # Dwell
         # Dwell
-        self.dwell_cb = FCCheckBox(_('Dwell:'))
+        self.dwell_cb = FCCheckBox('%s:' % _('Dwell'))
         self.dwell_cb.setToolTip(
         self.dwell_cb.setToolTip(
             _(
             _(
                 "Pause to allow the spindle to reach its\n"
                 "Pause to allow the spindle to reach its\n"
@@ -1237,9 +1223,7 @@ class GeometryObjectUI(ObjectUI):
         )
         )
         self.dwelltime_entry = FloatEntry()
         self.dwelltime_entry = FloatEntry()
         self.dwelltime_entry.setToolTip(
         self.dwelltime_entry.setToolTip(
-            _(
-                "Number of milliseconds for spindle to dwell."
-            )
+            _("Number of time units for spindle to dwell.")
         )
         )
         self.grid3.addWidget(self.dwell_cb, 15, 0)
         self.grid3.addWidget(self.dwell_cb, 15, 0)
         self.grid3.addWidget(self.dwelltime_entry, 15, 1)
         self.grid3.addWidget(self.dwelltime_entry, 15, 1)
@@ -1247,12 +1231,10 @@ class GeometryObjectUI(ObjectUI):
         self.ois_dwell_geo = OptionalInputSection(self.dwell_cb, [self.dwelltime_entry])
         self.ois_dwell_geo = OptionalInputSection(self.dwell_cb, [self.dwelltime_entry])
 
 
         # postprocessor selection
         # postprocessor selection
-        pp_label = QtWidgets.QLabel(_("PostProcessor:"))
+        pp_label = QtWidgets.QLabel('%s:' % _("PostProcessor"))
         pp_label.setToolTip(
         pp_label.setToolTip(
-            _(
-                "The Postprocessor file that dictates\n"
-                "the Machine Code (like GCode, RML, HPGL) output."
-            )
+            _("The Postprocessor file that dictates\n"
+              "the Machine Code (like GCode, RML, HPGL) output.")
         )
         )
         self.grid3.addWidget(pp_label, 16, 0)
         self.grid3.addWidget(pp_label, 16, 0)
         self.pp_geometry_name_cb = FCComboBox()
         self.pp_geometry_name_cb = FCComboBox()
@@ -1260,12 +1242,10 @@ class GeometryObjectUI(ObjectUI):
         self.grid3.addWidget(self.pp_geometry_name_cb, 16, 1)
         self.grid3.addWidget(self.pp_geometry_name_cb, 16, 1)
 
 
         # Probe depth
         # Probe depth
-        self.pdepth_label = QtWidgets.QLabel(_("Probe Z depth:"))
+        self.pdepth_label = QtWidgets.QLabel('%s:' % _("Probe Z depth"))
         self.pdepth_label.setToolTip(
         self.pdepth_label.setToolTip(
-            _(
-                "The maximum depth that the probe is allowed\n"
-                "to probe. Negative value, in current units."
-            )
+            _("The maximum depth that the probe is allowed\n"
+              "to probe. Negative value, in current units.")
         )
         )
         self.grid3.addWidget(self.pdepth_label, 17, 0)
         self.grid3.addWidget(self.pdepth_label, 17, 0)
         self.pdepth_entry = FCEntry()
         self.pdepth_entry = FCEntry()
@@ -1274,11 +1254,9 @@ class GeometryObjectUI(ObjectUI):
         self.pdepth_entry.setVisible(False)
         self.pdepth_entry.setVisible(False)
 
 
         # Probe feedrate
         # Probe feedrate
-        self.feedrate_probe_label = QtWidgets.QLabel(_("Feedrate Probe:"))
+        self.feedrate_probe_label = QtWidgets.QLabel('%s:' % _("Feedrate Probe"))
         self.feedrate_probe_label.setToolTip(
         self.feedrate_probe_label.setToolTip(
-            _(
-                "The feedrate used while the probe is probing."
-            )
+            _("The feedrate used while the probe is probing.")
         )
         )
         self.grid3.addWidget(self.feedrate_probe_label, 18, 0)
         self.grid3.addWidget(self.feedrate_probe_label, 18, 0)
         self.feedrate_probe_entry = FCEntry()
         self.feedrate_probe_entry = FCEntry()
@@ -1297,16 +1275,14 @@ class GeometryObjectUI(ObjectUI):
         # Button
         # Button
         self.generate_cnc_button = QtWidgets.QPushButton(_('Generate'))
         self.generate_cnc_button = QtWidgets.QPushButton(_('Generate'))
         self.generate_cnc_button.setToolTip(
         self.generate_cnc_button.setToolTip(
-            _(
-                "Generate the CNC Job object."
-            )
+            _("Generate the CNC Job object.")
         )
         )
         self.geo_param_box.addWidget(self.generate_cnc_button)
         self.geo_param_box.addWidget(self.generate_cnc_button)
 
 
         # ##############
         # ##############
         # Paint area ##
         # Paint area ##
         # ##############
         # ##############
-        self.paint_label = QtWidgets.QLabel(_('<b>Paint Area:</b>'))
+        self.paint_label = QtWidgets.QLabel('<b>%s</b>' % _('Paint Area'))
         self.paint_label.setToolTip(
         self.paint_label.setToolTip(
             _(
             _(
                 "Creates tool paths to cover the\n"
                 "Creates tool paths to cover the\n"
@@ -1320,9 +1296,7 @@ class GeometryObjectUI(ObjectUI):
         # GO Button
         # GO Button
         self.paint_tool_button = QtWidgets.QPushButton(_('Paint Tool'))
         self.paint_tool_button = QtWidgets.QPushButton(_('Paint Tool'))
         self.paint_tool_button.setToolTip(
         self.paint_tool_button.setToolTip(
-            _(
-                "Launch Paint Tool in Tools Tab."
-            )
+            _("Launch Paint Tool in Tools Tab.")
         )
         )
         self.geo_tools_box.addWidget(self.paint_tool_button)
         self.geo_tools_box.addWidget(self.paint_tool_button)
 
 
@@ -1353,10 +1327,10 @@ class CNCObjectUI(ObjectUI):
         self.offset_button.hide()
         self.offset_button.hide()
 
 
         # ## Plot options
         # ## Plot options
-        self.plot_options_label = QtWidgets.QLabel(_("<b>Plot Options:</b>"))
+        self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
         self.custom_box.addWidget(self.plot_options_label)
         self.custom_box.addWidget(self.plot_options_label)
 
 
-        self.cncplot_method_label = QtWidgets.QLabel(_("<b>Plot kind:</b>"))
+        self.cncplot_method_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot kind"))
         self.cncplot_method_label.setToolTip(
         self.cncplot_method_label.setToolTip(
             _(
             _(
                 "This selects the kind of geometries on the canvas to plot.\n"
                 "This selects the kind of geometries on the canvas to plot.\n"
@@ -1372,12 +1346,11 @@ class CNCObjectUI(ObjectUI):
             {"label": _("Cut"), "value": "cut"}
             {"label": _("Cut"), "value": "cut"}
         ], stretch=False)
         ], stretch=False)
 
 
-        self.annotation_label = QtWidgets.QLabel(_("<b>Display Annotation:</b>"))
+        self.annotation_label = QtWidgets.QLabel("<b>%s:</b>" % _("Display Annotation"))
         self.annotation_label.setToolTip(
         self.annotation_label.setToolTip(
-            _(
-                "This selects if to display text annotation on the plot.\n"
-                "When checked it will display numbers in order for each end\n"
-                "of a travel line."
+            _("This selects if to display text annotation on the plot.\n"
+              "When checked it will display numbers in order for each end\n"
+              "of a travel line."
             )
             )
         )
         )
         self.annotation_cb = FCCheckBox()
         self.annotation_cb = FCCheckBox()
@@ -1385,13 +1358,13 @@ class CNCObjectUI(ObjectUI):
         # ## Object name
         # ## Object name
         self.name_hlay = QtWidgets.QHBoxLayout()
         self.name_hlay = QtWidgets.QHBoxLayout()
         self.custom_box.addLayout(self.name_hlay)
         self.custom_box.addLayout(self.name_hlay)
-        name_label = QtWidgets.QLabel(_("<b>Name:</b>"))
+        name_label = QtWidgets.QLabel("<b>%s:</b>" % _("Name"))
         self.name_entry = FCEntry()
         self.name_entry = FCEntry()
         self.name_entry.setFocusPolicy(QtCore.Qt.StrongFocus)
         self.name_entry.setFocusPolicy(QtCore.Qt.StrongFocus)
         self.name_hlay.addWidget(name_label)
         self.name_hlay.addWidget(name_label)
         self.name_hlay.addWidget(self.name_entry)
         self.name_hlay.addWidget(self.name_entry)
 
 
-        self.t_distance_label = QtWidgets.QLabel(_("<b>Travelled dist.:</b>"))
+        self.t_distance_label = QtWidgets.QLabel("<b>%s:</b>" % _("Travelled dist."))
         self.t_distance_label.setToolTip(
         self.t_distance_label.setToolTip(
             _("This is the total travelled distance on X-Y plane.\n"
             _("This is the total travelled distance on X-Y plane.\n"
               "In current units.")
               "In current units.")
@@ -1403,7 +1376,7 @@ class CNCObjectUI(ObjectUI):
         )
         )
         self.units_label = QtWidgets.QLabel()
         self.units_label = QtWidgets.QLabel()
 
 
-        self.t_time_label = QtWidgets.QLabel(_("<b>Estimated time:</b>"))
+        self.t_time_label = QtWidgets.QLabel("<b>%s:</b>" % _("Estimated time"))
         self.t_time_label.setToolTip(
         self.t_time_label.setToolTip(
             _("This is the estimated time to do the routing/drilling,\n"
             _("This is the estimated time to do the routing/drilling,\n"
               "without the time spent in ToolChange events.")
               "without the time spent in ToolChange events.")
@@ -1445,7 +1418,7 @@ class CNCObjectUI(ObjectUI):
         self.custom_box.addLayout(hlay)
         self.custom_box.addLayout(hlay)
 
 
         # CNC Tools Table for plot
         # CNC Tools Table for plot
-        self.cnc_tools_table_label = QtWidgets.QLabel(_('<b>CNC Tools Table</b>'))
+        self.cnc_tools_table_label = QtWidgets.QLabel('<b>%s</b>' % _('CNC Tools Table'))
         self.cnc_tools_table_label.setToolTip(
         self.cnc_tools_table_label.setToolTip(
             _(
             _(
                 "Tools in this CNCJob object used for cutting.\n"
                 "Tools in this CNCJob object used for cutting.\n"
@@ -1465,9 +1438,7 @@ class CNCObjectUI(ObjectUI):
         # self.plot_cb = QtWidgets.QCheckBox('Plot')
         # self.plot_cb = QtWidgets.QCheckBox('Plot')
         self.plot_cb = FCCheckBox(_('Plot Object'))
         self.plot_cb = FCCheckBox(_('Plot Object'))
         self.plot_cb.setToolTip(
         self.plot_cb.setToolTip(
-            _(
-                "Plot (show) this object."
-            )
+            _("Plot (show) this object.")
         )
         )
         self.plot_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
         self.plot_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
         hlay.addStretch()
         hlay.addStretch()
@@ -1497,20 +1468,18 @@ class CNCObjectUI(ObjectUI):
         # ####################
         # ####################
         # ## Export G-Code ##
         # ## Export G-Code ##
         # ####################
         # ####################
-        self.export_gcode_label = QtWidgets.QLabel(_("<b>Export CNC Code:</b>"))
+        self.export_gcode_label = QtWidgets.QLabel("<b>%s:</b>" % _("Export CNC Code"))
         self.export_gcode_label.setToolTip(
         self.export_gcode_label.setToolTip(
             _("Export and save G-Code to\n"
             _("Export and save G-Code to\n"
-            "make this object to a file.")
+              "make this object to a file.")
         )
         )
         self.custom_box.addWidget(self.export_gcode_label)
         self.custom_box.addWidget(self.export_gcode_label)
 
 
         # Prepend text to GCode
         # Prepend text to GCode
-        prependlabel = QtWidgets.QLabel(_('Prepend to CNC Code:'))
+        prependlabel = QtWidgets.QLabel('%s:' % _('Prepend to CNC Code'))
         prependlabel.setToolTip(
         prependlabel.setToolTip(
-            _(
-                "Type here any G-Code commands you would\n"
-                "like to add to the beginning of the generated file."
-            )
+            _("Type here any G-Code commands you would\n"
+              "like to add at the beginning of the G-Code file.")
         )
         )
         self.custom_box.addWidget(prependlabel)
         self.custom_box.addWidget(prependlabel)
 
 
@@ -1518,13 +1487,11 @@ class CNCObjectUI(ObjectUI):
         self.custom_box.addWidget(self.prepend_text)
         self.custom_box.addWidget(self.prepend_text)
 
 
         # Append text to GCode
         # Append text to GCode
-        appendlabel = QtWidgets.QLabel(_('Append to CNC Code:'))
+        appendlabel = QtWidgets.QLabel('%s:' % _('Append to CNC Code'))
         appendlabel.setToolTip(
         appendlabel.setToolTip(
-            _(
-                "Type here any G-Code commands you would\n"
-                "like to append to the generated file.\n"
-                "I.e.: M2 (End of program)"
-            )
+            _("Type here any G-Code commands you would\n"
+              "like to append to the generated file.\n"
+              "I.e.: M2 (End of program)")
         )
         )
         self.custom_box.addWidget(appendlabel)
         self.custom_box.addWidget(appendlabel)
 
 
@@ -1539,7 +1506,7 @@ class CNCObjectUI(ObjectUI):
         self.cnc_frame.setLayout(self.cnc_box)
         self.cnc_frame.setLayout(self.cnc_box)
 
 
         # Toolchange Custom G-Code
         # Toolchange Custom G-Code
-        self.toolchangelabel = QtWidgets.QLabel(_('Toolchange G-Code:'))
+        self.toolchangelabel = QtWidgets.QLabel('%s:' % _('Toolchange G-Code'))
         self.toolchangelabel.setToolTip(
         self.toolchangelabel.setToolTip(
             _(
             _(
                 "Type here any G-Code commands you would\n"
                 "Type here any G-Code commands you would\n"
@@ -1561,12 +1528,10 @@ class CNCObjectUI(ObjectUI):
         self.cnc_box.addLayout(cnclay)
         self.cnc_box.addLayout(cnclay)
 
 
         # Toolchange Replacement Enable
         # Toolchange Replacement Enable
-        self.toolchange_cb = FCCheckBox(label=_('Use Toolchange Macro'))
+        self.toolchange_cb = FCCheckBox(label='%s' % _('Use Toolchange Macro'))
         self.toolchange_cb.setToolTip(
         self.toolchange_cb.setToolTip(
-            _(
-                "Check this box if you want to use\n"
-                "a Custom Toolchange GCode (macro)."
-            )
+            _("Check this box if you want to use\n"
+              "a Custom Toolchange GCode (macro).")
         )
         )
 
 
         # Variable list
         # Variable list
@@ -1612,19 +1577,15 @@ class CNCObjectUI(ObjectUI):
         # Edit GCode Button
         # Edit GCode Button
         self.modify_gcode_button = QtWidgets.QPushButton(_('View CNC Code'))
         self.modify_gcode_button = QtWidgets.QPushButton(_('View CNC Code'))
         self.modify_gcode_button.setToolTip(
         self.modify_gcode_button.setToolTip(
-            _(
-                "Opens TAB to view/modify/print G-Code\n"
-                "file."
-            )
+            _("Opens TAB to view/modify/print G-Code\n"
+              "file.")
         )
         )
 
 
         # GO Button
         # GO Button
         self.export_gcode_button = QtWidgets.QPushButton(_('Save CNC Code'))
         self.export_gcode_button = QtWidgets.QPushButton(_('Save CNC Code'))
         self.export_gcode_button.setToolTip(
         self.export_gcode_button.setToolTip(
-            _(
-                "Opens dialog to save G-Code\n"
-                "file."
-            )
+            _("Opens dialog to save G-Code\n"
+              "file.")
         )
         )
 
 
         h_lay.addWidget(self.modify_gcode_button)
         h_lay.addWidget(self.modify_gcode_button)

+ 20 - 14
flatcamTools/ToolCalculators.py

@@ -89,28 +89,29 @@ class ToolCalculator(FlatCAMTool):
         form_layout = QtWidgets.QFormLayout()
         form_layout = QtWidgets.QFormLayout()
         self.layout.addLayout(form_layout)
         self.layout.addLayout(form_layout)
 
 
-        self.tipDia_label = QtWidgets.QLabel(_("Tip Diameter:"))
+        self.tipDia_label = QtWidgets.QLabel('%s:' % _("Tip Diameter"))
         self.tipDia_entry = FCEntry()
         self.tipDia_entry = FCEntry()
         # self.tipDia_entry.setFixedWidth(70)
         # self.tipDia_entry.setFixedWidth(70)
         self.tipDia_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.tipDia_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
-        self.tipDia_label.setToolTip(_('This is the diameter of the tool tip.\n'
-                                       'The manufacturer specifies it.'))
-
-        self.tipAngle_label = QtWidgets.QLabel(_("Tip Angle:"))
+        self.tipDia_label.setToolTip(
+            _("This is the tool tip diameter.\n"
+              "It is specified by manufacturer.")
+        )
+        self.tipAngle_label = QtWidgets.QLabel('%s:' % _("Tip Angle"))
         self.tipAngle_entry = FCEntry()
         self.tipAngle_entry = FCEntry()
         # self.tipAngle_entry.setFixedWidth(70)
         # self.tipAngle_entry.setFixedWidth(70)
         self.tipAngle_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.tipAngle_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.tipAngle_label.setToolTip(_("This is the angle of the tip of the tool.\n"
         self.tipAngle_label.setToolTip(_("This is the angle of the tip of the tool.\n"
                                          "It is specified by manufacturer."))
                                          "It is specified by manufacturer."))
 
 
-        self.cutDepth_label = QtWidgets.QLabel(_("Cut Z:"))
+        self.cutDepth_label = QtWidgets.QLabel('%s:' % _("Cut Z"))
         self.cutDepth_entry = FCEntry()
         self.cutDepth_entry = FCEntry()
         # self.cutDepth_entry.setFixedWidth(70)
         # self.cutDepth_entry.setFixedWidth(70)
         self.cutDepth_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.cutDepth_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.cutDepth_label.setToolTip(_("This is the depth to cut into the material.\n"
         self.cutDepth_label.setToolTip(_("This is the depth to cut into the material.\n"
                                          "In the CNCJob is the CutZ parameter."))
                                          "In the CNCJob is the CutZ parameter."))
 
 
-        self.effectiveToolDia_label = QtWidgets.QLabel(_("Tool Diameter:"))
+        self.effectiveToolDia_label = QtWidgets.QLabel('%s:' % _("Tool Diameter"))
         self.effectiveToolDia_entry = FCEntry()
         self.effectiveToolDia_entry = FCEntry()
         # self.effectiveToolDia_entry.setFixedWidth(70)
         # self.effectiveToolDia_entry.setFixedWidth(70)
         self.effectiveToolDia_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.effectiveToolDia_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
@@ -154,26 +155,26 @@ class ToolCalculator(FlatCAMTool):
         plate_form_layout = QtWidgets.QFormLayout()
         plate_form_layout = QtWidgets.QFormLayout()
         self.layout.addLayout(plate_form_layout)
         self.layout.addLayout(plate_form_layout)
 
 
-        self.pcblengthlabel = QtWidgets.QLabel(_("Board Length:"))
+        self.pcblengthlabel = QtWidgets.QLabel('%s:' % _("Board Length"))
         self.pcblength_entry = FCEntry()
         self.pcblength_entry = FCEntry()
         # self.pcblengthlabel.setFixedWidth(70)
         # self.pcblengthlabel.setFixedWidth(70)
         self.pcblength_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.pcblength_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.pcblengthlabel.setToolTip(_('This is the board length. In centimeters.'))
         self.pcblengthlabel.setToolTip(_('This is the board length. In centimeters.'))
 
 
-        self.pcbwidthlabel = QtWidgets.QLabel(_("Board Width:"))
+        self.pcbwidthlabel = QtWidgets.QLabel('%s:' % _("Board Width"))
         self.pcbwidth_entry = FCEntry()
         self.pcbwidth_entry = FCEntry()
         # self.pcbwidthlabel.setFixedWidth(70)
         # self.pcbwidthlabel.setFixedWidth(70)
         self.pcbwidth_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.pcbwidth_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.pcbwidthlabel.setToolTip(_('This is the board width.In centimeters.'))
         self.pcbwidthlabel.setToolTip(_('This is the board width.In centimeters.'))
 
 
-        self.cdensity_label = QtWidgets.QLabel(_("Current Density:"))
+        self.cdensity_label = QtWidgets.QLabel('%s:' % _("Current Density"))
         self.cdensity_entry = FCEntry()
         self.cdensity_entry = FCEntry()
         # self.cdensity_entry.setFixedWidth(70)
         # self.cdensity_entry.setFixedWidth(70)
         self.cdensity_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.cdensity_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.cdensity_label.setToolTip(_("Current density to pass through the board. \n"
         self.cdensity_label.setToolTip(_("Current density to pass through the board. \n"
                                          "In Amps per Square Feet ASF."))
                                          "In Amps per Square Feet ASF."))
 
 
-        self.growth_label = QtWidgets.QLabel(_("Copper Growth:"))
+        self.growth_label = QtWidgets.QLabel('%s:' % _("Copper Growth"))
         self.growth_entry = FCEntry()
         self.growth_entry = FCEntry()
         # self.growth_entry.setFixedWidth(70)
         # self.growth_entry.setFixedWidth(70)
         self.growth_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.growth_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
@@ -182,7 +183,7 @@ class ToolCalculator(FlatCAMTool):
 
 
         # self.growth_entry.setEnabled(False)
         # self.growth_entry.setEnabled(False)
 
 
-        self.cvaluelabel = QtWidgets.QLabel(_("Current Value:"))
+        self.cvaluelabel = QtWidgets.QLabel('%s:' % _("Current Value"))
         self.cvalue_entry = FCEntry()
         self.cvalue_entry = FCEntry()
         # self.cvaluelabel.setFixedWidth(70)
         # self.cvaluelabel.setFixedWidth(70)
         self.cvalue_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.cvalue_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
@@ -190,7 +191,7 @@ class ToolCalculator(FlatCAMTool):
                                       'to be set on the Power Supply. In Amps.'))
                                       'to be set on the Power Supply. In Amps.'))
         self.cvalue_entry.setDisabled(True)
         self.cvalue_entry.setDisabled(True)
 
 
-        self.timelabel = QtWidgets.QLabel(_("Time:"))
+        self.timelabel = QtWidgets.QLabel('%s:' % _("Time"))
         self.time_entry = FCEntry()
         self.time_entry = FCEntry()
         # self.timelabel.setFixedWidth(70)
         # self.timelabel.setFixedWidth(70)
         self.time_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.time_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
@@ -242,7 +243,12 @@ class ToolCalculator(FlatCAMTool):
             else:
             else:
                 try:
                 try:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
-                        self.app.ui.splitter.setSizes([0, 1])
+                        # if tab is populated with the tool but it does not have the focus, focus on it
+                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
+                            # focus on Tool Tab
+                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
+                        else:
+                            self.app.ui.splitter.setSizes([0, 1])
                 except AttributeError:
                 except AttributeError:
                     pass
                     pass
         else:
         else:

+ 207 - 104
flatcamTools/ToolCutOut.py

@@ -51,7 +51,7 @@ class CutOut(FlatCAMTool):
         # self.type_obj_combo.setItemIcon(1, QtGui.QIcon("share/drill16.png"))
         # self.type_obj_combo.setItemIcon(1, QtGui.QIcon("share/drill16.png"))
         self.type_obj_combo.setItemIcon(2, QtGui.QIcon("share/geometry16.png"))
         self.type_obj_combo.setItemIcon(2, QtGui.QIcon("share/geometry16.png"))
 
 
-        self.type_obj_combo_label = QtWidgets.QLabel(_("Obj Type:"))
+        self.type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Obj Type"))
         self.type_obj_combo_label.setToolTip(
         self.type_obj_combo_label.setToolTip(
             _("Specify the type of object to be cutout.\n"
             _("Specify the type of object to be cutout.\n"
               "It can be of type: Gerber or Geometry.\n"
               "It can be of type: Gerber or Geometry.\n"
@@ -67,14 +67,14 @@ class CutOut(FlatCAMTool):
         self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.obj_combo.setCurrentIndex(1)
         self.obj_combo.setCurrentIndex(1)
 
 
-        self.object_label = QtWidgets.QLabel(_("Object:"))
+        self.object_label = QtWidgets.QLabel('%s:' % _("Object"))
         self.object_label.setToolTip(
         self.object_label.setToolTip(
             _("Object to be cutout.                        ")
             _("Object to be cutout.                        ")
         )
         )
         form_layout.addRow(self.object_label, self.obj_combo)
         form_layout.addRow(self.object_label, self.obj_combo)
 
 
         # Object kind
         # Object kind
-        self.kindlabel = QtWidgets.QLabel(_('Obj kind:'))
+        self.kindlabel = QtWidgets.QLabel('%s:' % _('Obj kind'))
         self.kindlabel.setToolTip(
         self.kindlabel.setToolTip(
             _("Choice of what kind the object we want to cutout is.<BR>"
             _("Choice of what kind the object we want to cutout is.<BR>"
               "- <B>Single</B>: contain a single PCB Gerber outline object.<BR>"
               "- <B>Single</B>: contain a single PCB Gerber outline object.<BR>"
@@ -89,7 +89,7 @@ class CutOut(FlatCAMTool):
 
 
         # Tool Diameter
         # Tool Diameter
         self.dia = FCEntry()
         self.dia = FCEntry()
-        self.dia_label = QtWidgets.QLabel(_("Tool Dia:"))
+        self.dia_label = QtWidgets.QLabel('%s:' % _("Tool dia"))
         self.dia_label.setToolTip(
         self.dia_label.setToolTip(
            _("Diameter of the tool used to cutout\n"
            _("Diameter of the tool used to cutout\n"
              "the PCB shape out of the surrounding material.")
              "the PCB shape out of the surrounding material.")
@@ -98,7 +98,7 @@ class CutOut(FlatCAMTool):
 
 
         # Margin
         # Margin
         self.margin = FCEntry()
         self.margin = FCEntry()
-        self.margin_label = QtWidgets.QLabel(_("Margin:"))
+        self.margin_label = QtWidgets.QLabel('%s:' % _("Margin:"))
         self.margin_label.setToolTip(
         self.margin_label.setToolTip(
            _("Margin over bounds. A positive value here\n"
            _("Margin over bounds. A positive value here\n"
              "will make the cutout of the PCB further from\n"
              "will make the cutout of the PCB further from\n"
@@ -108,7 +108,7 @@ class CutOut(FlatCAMTool):
 
 
         # Gapsize
         # Gapsize
         self.gapsize = FCEntry()
         self.gapsize = FCEntry()
-        self.gapsize_label = QtWidgets.QLabel(_("Gap size:"))
+        self.gapsize_label = QtWidgets.QLabel('%s:' % _("Gap size:"))
         self.gapsize_label.setToolTip(
         self.gapsize_label.setToolTip(
            _("The size of the bridge gaps in the cutout\n"
            _("The size of the bridge gaps in the cutout\n"
              "used to keep the board connected to\n"
              "used to keep the board connected to\n"
@@ -127,7 +127,7 @@ class CutOut(FlatCAMTool):
 
 
         # Surrounding convex box shape
         # Surrounding convex box shape
         self.convex_box = FCCheckBox()
         self.convex_box = FCCheckBox()
-        self.convex_box_label = QtWidgets.QLabel(_("Convex Sh.:"))
+        self.convex_box_label = QtWidgets.QLabel('%s:' % _("Convex Sh."))
         self.convex_box_label.setToolTip(
         self.convex_box_label.setToolTip(
             _("Create a convex shape surrounding the entire PCB.\n"
             _("Create a convex shape surrounding the entire PCB.\n"
               "Used only if the source object type is Gerber.")
               "Used only if the source object type is Gerber.")
@@ -146,11 +146,12 @@ class CutOut(FlatCAMTool):
         self.layout.addLayout(form_layout_2)
         self.layout.addLayout(form_layout_2)
 
 
         # Gaps
         # Gaps
-        gaps_label = QtWidgets.QLabel(_('Gaps:'))
+        gaps_label = QtWidgets.QLabel('%s:' % _('Gaps'))
         gaps_label.setToolTip(
         gaps_label.setToolTip(
             _("Number of gaps used for the Automatic cutout.\n"
             _("Number of gaps used for the Automatic cutout.\n"
               "There can be maximum 8 bridges/gaps.\n"
               "There can be maximum 8 bridges/gaps.\n"
               "The choices are:\n"
               "The choices are:\n"
+              "- None  - no gaps\n"
               "- lr    - left + right\n"
               "- lr    - left + right\n"
               "- tb    - top + bottom\n"
               "- tb    - top + bottom\n"
               "- 4     - left + right +top + bottom\n"
               "- 4     - left + right +top + bottom\n"
@@ -161,7 +162,7 @@ class CutOut(FlatCAMTool):
         gaps_label.setMinimumWidth(60)
         gaps_label.setMinimumWidth(60)
 
 
         self.gaps = FCComboBox()
         self.gaps = FCComboBox()
-        gaps_items = ['LR', 'TB', '4', '2LR', '2TB', '8']
+        gaps_items = ['None', 'LR', 'TB', '4', '2LR', '2TB', '8']
         for it in gaps_items:
         for it in gaps_items:
             self.gaps.addItem(it)
             self.gaps.addItem(it)
             self.gaps.setStyleSheet('background-color: rgb(255,255,255)')
             self.gaps.setStyleSheet('background-color: rgb(255,255,255)')
@@ -171,7 +172,7 @@ class CutOut(FlatCAMTool):
         hlay = QtWidgets.QHBoxLayout()
         hlay = QtWidgets.QHBoxLayout()
         self.layout.addLayout(hlay)
         self.layout.addLayout(hlay)
 
 
-        title_ff_label = QtWidgets.QLabel("<b>%s</b>" % _('FreeForm:'))
+        title_ff_label = QtWidgets.QLabel("<b>%s:</b>" % _('FreeForm'))
         title_ff_label.setToolTip(
         title_ff_label.setToolTip(
             _("The cutout shape can be of ny shape.\n"
             _("The cutout shape can be of ny shape.\n"
               "Useful when the PCB has a non-rectangular shape.")
               "Useful when the PCB has a non-rectangular shape.")
@@ -191,7 +192,7 @@ class CutOut(FlatCAMTool):
         hlay2 = QtWidgets.QHBoxLayout()
         hlay2 = QtWidgets.QHBoxLayout()
         self.layout.addLayout(hlay2)
         self.layout.addLayout(hlay2)
 
 
-        title_rct_label = QtWidgets.QLabel("<b>%s</b>" % _('Rectangular:'))
+        title_rct_label = QtWidgets.QLabel("<b>%s:</b>" % _('Rectangular'))
         title_rct_label.setToolTip(
         title_rct_label.setToolTip(
             _("The resulting cutout shape is\n"
             _("The resulting cutout shape is\n"
               "always a rectangle shape and it will be\n"
               "always a rectangle shape and it will be\n"
@@ -228,7 +229,7 @@ class CutOut(FlatCAMTool):
         self.man_object_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
         self.man_object_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
         self.man_object_combo.setCurrentIndex(1)
         self.man_object_combo.setCurrentIndex(1)
 
 
-        self.man_object_label = QtWidgets.QLabel(_("Geo Obj:"))
+        self.man_object_label = QtWidgets.QLabel('%s:' % _("Geo Obj"))
         self.man_object_label.setToolTip(
         self.man_object_label.setToolTip(
             _("Geometry object used to create the manual cutout.")
             _("Geometry object used to create the manual cutout.")
         )
         )
@@ -241,7 +242,7 @@ class CutOut(FlatCAMTool):
         hlay3 = QtWidgets.QHBoxLayout()
         hlay3 = QtWidgets.QHBoxLayout()
         self.layout.addLayout(hlay3)
         self.layout.addLayout(hlay3)
 
 
-        self.man_geo_label = QtWidgets.QLabel(_("Manual Geo:"))
+        self.man_geo_label = QtWidgets.QLabel('%s:' % _("Manual Geo"))
         self.man_geo_label.setToolTip(
         self.man_geo_label.setToolTip(
             _("If the object to be cutout is a Gerber\n"
             _("If the object to be cutout is a Gerber\n"
               "first create a Geometry that surrounds it,\n"
               "first create a Geometry that surrounds it,\n"
@@ -263,7 +264,7 @@ class CutOut(FlatCAMTool):
         hlay4 = QtWidgets.QHBoxLayout()
         hlay4 = QtWidgets.QHBoxLayout()
         self.layout.addLayout(hlay4)
         self.layout.addLayout(hlay4)
 
 
-        self.man_bridge_gaps_label = QtWidgets.QLabel(_("Manual Add Bridge Gaps:"))
+        self.man_bridge_gaps_label = QtWidgets.QLabel('%s:' % _("Manual Add Bridge Gaps"))
         self.man_bridge_gaps_label.setToolTip(
         self.man_bridge_gaps_label.setToolTip(
             _("Use the left mouse button (LMB) click\n"
             _("Use the left mouse button (LMB) click\n"
               "to create a bridge gap to separate the PCB from\n"
               "to create a bridge gap to separate the PCB from\n"
@@ -292,6 +293,9 @@ class CutOut(FlatCAMTool):
 
 
         self.flat_geometry = []
         self.flat_geometry = []
 
 
+        # this is the Geometry object generated in this class to be used for adding manual gaps
+        self.man_cutout_obj = None
+
         # Signals
         # Signals
         self.ff_cutout_object_btn.clicked.connect(self.on_freeform_cutout)
         self.ff_cutout_object_btn.clicked.connect(self.on_freeform_cutout)
         self.rect_cutout_object_btn.clicked.connect(self.on_rectangular_cutout)
         self.rect_cutout_object_btn.clicked.connect(self.on_rectangular_cutout)
@@ -315,7 +319,12 @@ class CutOut(FlatCAMTool):
             else:
             else:
                 try:
                 try:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
-                        self.app.ui.splitter.setSizes([0, 1])
+                        # if tab is populated with the tool but it does not have the focus, focus on it
+                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
+                            # focus on Tool Tab
+                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
+                        else:
+                            self.app.ui.splitter.setSizes([0, 1])
                 except AttributeError:
                 except AttributeError:
                     pass
                     pass
         else:
         else:
@@ -410,8 +419,9 @@ class CutOut(FlatCAMTool):
             self.app.inform.emit(_("[WARNING_NOTCL] Number of gaps value is missing. Add it and retry."))
             self.app.inform.emit(_("[WARNING_NOTCL] Number of gaps value is missing. Add it and retry."))
             return
             return
 
 
-        if gaps not in ['LR', 'TB', '2LR', '2TB', '4', '8']:
-            self.app.inform.emit(_("[WARNING_NOTCL] Gaps value can be only one of: 'lr', 'tb', '2lr', '2tb', 4 or 8. "
+        if gaps not in ['None', 'LR', 'TB', '2LR', '2TB', '4', '8']:
+            self.app.inform.emit(_("[WARNING_NOTCL] Gaps value can be only one of: "
+                                   "'None', 'lr', 'tb', '2lr', '2tb', 4 or 8. "
                                    "Fill in a correct value and retry. "))
                                    "Fill in a correct value and retry. "))
             return
             return
 
 
@@ -446,44 +456,46 @@ class CutOut(FlatCAMTool):
                 leny = (ymax - ymin) + (margin * 2)
                 leny = (ymax - ymin) + (margin * 2)
 
 
                 proc_geometry = []
                 proc_geometry = []
-
-                if gaps == '8' or gaps == '2LR':
-                    geom = self.subtract_poly_from_geo(geom,
-                                                       xmin - gapsize,  # botleft_x
-                                                       py - gapsize + leny / 4,  # botleft_y
-                                                       xmax + gapsize,  # topright_x
-                                                       py + gapsize + leny / 4)  # topright_y
-                    geom = self.subtract_poly_from_geo(geom,
-                                                       xmin - gapsize,
-                                                       py - gapsize - leny / 4,
-                                                       xmax + gapsize,
-                                                       py + gapsize - leny / 4)
-
-                if gaps == '8' or gaps == '2TB':
-                    geom = self.subtract_poly_from_geo(geom,
-                                                       px - gapsize + lenx / 4,
-                                                       ymin - gapsize,
-                                                       px + gapsize + lenx / 4,
-                                                       ymax + gapsize)
-                    geom = self.subtract_poly_from_geo(geom,
-                                                       px - gapsize - lenx / 4,
-                                                       ymin - gapsize,
-                                                       px + gapsize - lenx / 4,
-                                                       ymax + gapsize)
-
-                if gaps == '4' or gaps == 'LR':
-                    geom = self.subtract_poly_from_geo(geom,
-                                                       xmin - gapsize,
-                                                       py - gapsize,
-                                                       xmax + gapsize,
-                                                       py + gapsize)
-
-                if gaps == '4' or gaps == 'TB':
-                    geom = self.subtract_poly_from_geo(geom,
-                                                       px - gapsize,
-                                                       ymin - gapsize,
-                                                       px + gapsize,
-                                                       ymax + gapsize)
+                if gaps == 'None':
+                    pass
+                else:
+                    if gaps == '8' or gaps == '2LR':
+                        geom = self.subtract_poly_from_geo(geom,
+                                                           xmin - gapsize,  # botleft_x
+                                                           py - gapsize + leny / 4,  # botleft_y
+                                                           xmax + gapsize,  # topright_x
+                                                           py + gapsize + leny / 4)  # topright_y
+                        geom = self.subtract_poly_from_geo(geom,
+                                                           xmin - gapsize,
+                                                           py - gapsize - leny / 4,
+                                                           xmax + gapsize,
+                                                           py + gapsize - leny / 4)
+
+                    if gaps == '8' or gaps == '2TB':
+                        geom = self.subtract_poly_from_geo(geom,
+                                                           px - gapsize + lenx / 4,
+                                                           ymin - gapsize,
+                                                           px + gapsize + lenx / 4,
+                                                           ymax + gapsize)
+                        geom = self.subtract_poly_from_geo(geom,
+                                                           px - gapsize - lenx / 4,
+                                                           ymin - gapsize,
+                                                           px + gapsize - lenx / 4,
+                                                           ymax + gapsize)
+
+                    if gaps == '4' or gaps == 'LR':
+                        geom = self.subtract_poly_from_geo(geom,
+                                                           xmin - gapsize,
+                                                           py - gapsize,
+                                                           xmax + gapsize,
+                                                           py + gapsize)
+
+                    if gaps == '4' or gaps == 'TB':
+                        geom = self.subtract_poly_from_geo(geom,
+                                                           px - gapsize,
+                                                           ymin - gapsize,
+                                                           px + gapsize,
+                                                           ymax + gapsize)
 
 
                 try:
                 try:
                     for g in geom:
                     for g in geom:
@@ -603,8 +615,9 @@ class CutOut(FlatCAMTool):
             self.app.inform.emit(_("[WARNING_NOTCL] Number of gaps value is missing. Add it and retry."))
             self.app.inform.emit(_("[WARNING_NOTCL] Number of gaps value is missing. Add it and retry."))
             return
             return
 
 
-        if gaps not in ['LR', 'TB', '2LR', '2TB', '4', '8']:
-            self.app.inform.emit(_("[WARNING_NOTCL] Gaps value can be only one of: 'lr', 'tb', '2lr', '2tb', 4 or 8. "
+        if gaps not in ['None', 'LR', 'TB', '2LR', '2TB', '4', '8']:
+            self.app.inform.emit(_("[WARNING_NOTCL] Gaps value can be only one of: "
+                                   "'None', 'lr', 'tb', '2lr', '2tb', 4 or 8. "
                                    "Fill in a correct value and retry. "))
                                    "Fill in a correct value and retry. "))
             return
             return
 
 
@@ -630,43 +643,46 @@ class CutOut(FlatCAMTool):
                 lenx = (xmax - xmin) + (margin * 2)
                 lenx = (xmax - xmin) + (margin * 2)
                 leny = (ymax - ymin) + (margin * 2)
                 leny = (ymax - ymin) + (margin * 2)
 
 
-                if gaps == '8' or gaps == '2LR':
-                    geom = self.subtract_poly_from_geo(geom,
-                                                       xmin - gapsize,  # botleft_x
-                                                       py - gapsize + leny / 4,  # botleft_y
-                                                       xmax + gapsize,  # topright_x
-                                                       py + gapsize + leny / 4)  # topright_y
-                    geom = self.subtract_poly_from_geo(geom,
-                                                       xmin - gapsize,
-                                                       py - gapsize - leny / 4,
-                                                       xmax + gapsize,
-                                                       py + gapsize - leny / 4)
-
-                if gaps == '8' or gaps == '2TB':
-                    geom = self.subtract_poly_from_geo(geom,
-                                                       px - gapsize + lenx / 4,
-                                                       ymin - gapsize,
-                                                       px + gapsize + lenx / 4,
-                                                       ymax + gapsize)
-                    geom = self.subtract_poly_from_geo(geom,
-                                                       px - gapsize - lenx / 4,
-                                                       ymin - gapsize,
-                                                       px + gapsize - lenx / 4,
-                                                       ymax + gapsize)
-
-                if gaps == '4' or gaps == 'LR':
-                    geom = self.subtract_poly_from_geo(geom,
-                                                       xmin - gapsize,
-                                                       py - gapsize,
-                                                       xmax + gapsize,
-                                                       py + gapsize)
-
-                if gaps == '4' or gaps == 'TB':
-                    geom = self.subtract_poly_from_geo(geom,
-                                                       px - gapsize,
-                                                       ymin - gapsize,
-                                                       px + gapsize,
-                                                       ymax + gapsize)
+                if gaps == 'None':
+                    pass
+                else:
+                    if gaps == '8' or gaps == '2LR':
+                        geom = self.subtract_poly_from_geo(geom,
+                                                           xmin - gapsize,  # botleft_x
+                                                           py - gapsize + leny / 4,  # botleft_y
+                                                           xmax + gapsize,  # topright_x
+                                                           py + gapsize + leny / 4)  # topright_y
+                        geom = self.subtract_poly_from_geo(geom,
+                                                           xmin - gapsize,
+                                                           py - gapsize - leny / 4,
+                                                           xmax + gapsize,
+                                                           py + gapsize - leny / 4)
+
+                    if gaps == '8' or gaps == '2TB':
+                        geom = self.subtract_poly_from_geo(geom,
+                                                           px - gapsize + lenx / 4,
+                                                           ymin - gapsize,
+                                                           px + gapsize + lenx / 4,
+                                                           ymax + gapsize)
+                        geom = self.subtract_poly_from_geo(geom,
+                                                           px - gapsize - lenx / 4,
+                                                           ymin - gapsize,
+                                                           px + gapsize - lenx / 4,
+                                                           ymax + gapsize)
+
+                    if gaps == '4' or gaps == 'LR':
+                        geom = self.subtract_poly_from_geo(geom,
+                                                           xmin - gapsize,
+                                                           py - gapsize,
+                                                           xmax + gapsize,
+                                                           py + gapsize)
+
+                    if gaps == '4' or gaps == 'TB':
+                        geom = self.subtract_poly_from_geo(geom,
+                                                           px - gapsize,
+                                                           ymin - gapsize,
+                                                           px + gapsize,
+                                                           ymax + gapsize)
                 try:
                 try:
                     for g in geom:
                     for g in geom:
                         proc_geometry.append(g)
                         proc_geometry.append(g)
@@ -743,6 +759,15 @@ class CutOut(FlatCAMTool):
                                      "Add it and retry."))
                                      "Add it and retry."))
                 return
                 return
 
 
+        name = self.man_object_combo.currentText()
+        # Get Geometry source object to be used as target for Manual adding Gaps
+        try:
+            self.man_cutout_obj = self.app.collection.get_by_name(str(name))
+        except Exception as e:
+            log.debug("CutOut.on_manual_cutout() --> %s" % str(e))
+            self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve Geometry object: %s") % name)
+            return "Could not retrieve object: %s" % name
+
         self.app.plotcanvas.vis_disconnect('key_press', self.app.ui.keyPressEvent)
         self.app.plotcanvas.vis_disconnect('key_press', self.app.ui.keyPressEvent)
         self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
         self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
         self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
         self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
@@ -770,29 +795,43 @@ class CutOut(FlatCAMTool):
             self.app.geo_editor.tool_shape.clear(update=True)
             self.app.geo_editor.tool_shape.clear(update=True)
             self.app.geo_editor.tool_shape.enabled = False
             self.app.geo_editor.tool_shape.enabled = False
             self.gapFinished.emit()
             self.gapFinished.emit()
+        # if RMB then we exit
+        elif event.button == 2:
+            self.app.plotcanvas.vis_disconnect('key_press', self.on_key_press)
+            self.app.plotcanvas.vis_disconnect('mouse_move', self.on_mouse_move)
+            self.app.plotcanvas.vis_disconnect('mouse_release', self.doit)
+            self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent)
+            self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
+            self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
+
+            # Remove any previous utility shape
+            self.app.geo_editor.tool_shape.clear(update=True)
+            self.app.geo_editor.tool_shape.enabled = False
 
 
     def on_manual_cutout(self, click_pos):
     def on_manual_cutout(self, click_pos):
         name = self.man_object_combo.currentText()
         name = self.man_object_combo.currentText()
 
 
         # Get source object.
         # Get source object.
         try:
         try:
-            cutout_obj = self.app.collection.get_by_name(str(name))
+            self.man_cutout_obj = self.app.collection.get_by_name(str(name))
         except Exception as e:
         except Exception as e:
             log.debug("CutOut.on_manual_cutout() --> %s" % str(e))
             log.debug("CutOut.on_manual_cutout() --> %s" % str(e))
             self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve Geometry object: %s") % name)
             self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve Geometry object: %s") % name)
             return "Could not retrieve object: %s" % name
             return "Could not retrieve object: %s" % name
 
 
-        if cutout_obj is None:
-            self.app.inform.emit(_("[ERROR_NOTCL] Geometry object for manual cutout not found: %s") % cutout_obj)
+        if self.man_cutout_obj is None:
+            self.app.inform.emit(
+                _("[ERROR_NOTCL] Geometry object for manual cutout not found: %s") % self.man_cutout_obj)
             return
             return
 
 
         # use the snapped position as reference
         # use the snapped position as reference
         snapped_pos = self.app.geo_editor.snap(click_pos[0], click_pos[1])
         snapped_pos = self.app.geo_editor.snap(click_pos[0], click_pos[1])
 
 
         cut_poly = self.cutting_geo(pos=(snapped_pos[0], snapped_pos[1]))
         cut_poly = self.cutting_geo(pos=(snapped_pos[0], snapped_pos[1]))
-        cutout_obj.subtract_polygon(cut_poly)
+        self.man_cutout_obj.subtract_polygon(cut_poly)
 
 
-        cutout_obj.plot()
+        self.man_cutout_obj.plot()
         self.app.inform.emit(_("[success] Added manual Bridge Gap."))
         self.app.inform.emit(_("[success] Added manual Bridge Gap."))
 
 
         self.app.should_we_save = True
         self.app.should_we_save = True
@@ -864,9 +903,17 @@ class CutOut(FlatCAMTool):
                 geo = geo_union.convex_hull
                 geo = geo_union.convex_hull
                 geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
                 geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
             elif kind == 'single':
             elif kind == 'single':
-                x0, y0, x1, y1 = geo_union.bounds
-                geo = box(x0, y0, x1, y1)
-                geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
+                if isinstance(geo_union, Polygon) or \
+                        (isinstance(geo_union, list) and len(geo_union) == 1) or \
+                        (isinstance(geo_union, MultiPolygon) and len(geo_union) == 1):
+                    geo_obj.solid_geometry = geo_union.buffer(margin + abs(dia / 2)).exterior
+                elif isinstance(geo_union, MultiPolygon):
+                    x0, y0, x1, y1 = geo_union.bounds
+                    geo = box(x0, y0, x1, y1)
+                    geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
+                else:
+                    self.app.inform.emit(_("[ERROR_NOTCL] Geometry not supported for cutout: %s") % type(geo_union))
+                    return 'fail'
             else:
             else:
                 geo = geo_union
                 geo = geo_union
                 geo = geo.buffer(margin + abs(dia / 2))
                 geo = geo.buffer(margin + abs(dia / 2))
@@ -911,11 +958,67 @@ class CutOut(FlatCAMTool):
 
 
         snap_x, snap_y = self.app.geo_editor.snap(x, y)
         snap_x, snap_y = self.app.geo_editor.snap(x, y)
 
 
-        geo = self.cutting_geo(pos=(snap_x, snap_y))
+        # #################################################
+        # ### This section makes the cutting geo to #######
+        # ### rotate if it intersects the target geo ######
+        # #################################################
+        cut_geo = self.cutting_geo(pos=(snap_x, snap_y))
+        man_geo = self.man_cutout_obj.solid_geometry
+
+        def get_angle(geo):
+            line = cut_geo.intersection(geo)
+
+            try:
+                pt1_x = line.coords[0][0]
+                pt1_y = line.coords[0][1]
+                pt2_x = line.coords[1][0]
+                pt2_y = line.coords[1][1]
+                dx = pt1_x - pt2_x
+                dy = pt1_y - pt2_y
+
+                if dx == 0 or dy == 0:
+                    angle = 0
+                else:
+                    radian = math.atan(dx / dy)
+                    angle = radian * 180 / math.pi
+            except Exception as e:
+                angle = 0
+            return angle
+
+        try:
+            rot_angle = 0
+            for geo_el in man_geo:
+                if isinstance(geo_el, Polygon):
+                    work_geo = geo_el.exterior
+                    if cut_geo.intersects(work_geo):
+                        rot_angle = get_angle(geo=work_geo)
+                    else:
+                        rot_angle = 0
+                else:
+                    rot_angle = 0
+                    if cut_geo.intersects(geo_el):
+                        rot_angle = get_angle(geo=geo_el)
+                if rot_angle != 0:
+                    break
+        except TypeError:
+            if isinstance(man_geo, Polygon):
+                work_geo = man_geo.exterior
+                if cut_geo.intersects(work_geo):
+                    rot_angle = get_angle(geo=work_geo)
+                else:
+                    rot_angle = 0
+            else:
+                rot_angle = 0
+                if cut_geo.intersects(man_geo):
+                    rot_angle = get_angle(geo=man_geo)
+
+        # rotate only if there is an angle to rotate to
+        if rot_angle != 0:
+            cut_geo = affinity.rotate(cut_geo, -rot_angle)
 
 
         # Remove any previous utility shape
         # Remove any previous utility shape
         self.app.geo_editor.tool_shape.clear(update=True)
         self.app.geo_editor.tool_shape.clear(update=True)
-        self.draw_utility_geometry(geo=geo)
+        self.draw_utility_geometry(geo=cut_geo)
 
 
     def draw_utility_geometry(self, geo):
     def draw_utility_geometry(self, geo):
         self.app.geo_editor.tool_shape.add(
         self.app.geo_editor.tool_shape.add(

+ 13 - 8
flatcamTools/ToolDblSided.py

@@ -44,7 +44,7 @@ class DblSidedTool(FlatCAMTool):
         self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.gerber_object_combo.setCurrentIndex(1)
         self.gerber_object_combo.setCurrentIndex(1)
 
 
-        self.botlay_label = QtWidgets.QLabel(_("<b>GERBER:</b>"))
+        self.botlay_label = QtWidgets.QLabel("<b>%s:</b>" % _("GERBER"))
         self.botlay_label.setToolTip(
         self.botlay_label.setToolTip(
             "Gerber  to be mirrored."
             "Gerber  to be mirrored."
         )
         )
@@ -68,7 +68,7 @@ class DblSidedTool(FlatCAMTool):
         self.exc_object_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
         self.exc_object_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
         self.exc_object_combo.setCurrentIndex(1)
         self.exc_object_combo.setCurrentIndex(1)
 
 
-        self.excobj_label = QtWidgets.QLabel(_("<b>EXCELLON:</b>"))
+        self.excobj_label = QtWidgets.QLabel("<b>%s:</b>" % _("EXCELLON"))
         self.excobj_label.setToolTip(
         self.excobj_label.setToolTip(
             _("Excellon Object to be mirrored.")
             _("Excellon Object to be mirrored.")
         )
         )
@@ -92,7 +92,7 @@ class DblSidedTool(FlatCAMTool):
         self.geo_object_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
         self.geo_object_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
         self.geo_object_combo.setCurrentIndex(1)
         self.geo_object_combo.setCurrentIndex(1)
 
 
-        self.geoobj_label = QtWidgets.QLabel(_("<b>GEOMETRY</b>:"))
+        self.geoobj_label = QtWidgets.QLabel("<b>%s</b>:" % _("GEOMETRY"))
         self.geoobj_label.setToolTip(
         self.geoobj_label.setToolTip(
             _("Geometry Obj to be mirrored.")
             _("Geometry Obj to be mirrored.")
         )
         )
@@ -149,7 +149,7 @@ class DblSidedTool(FlatCAMTool):
 
 
         # ## Point/Box
         # ## Point/Box
         self.point_box_container = QtWidgets.QVBoxLayout()
         self.point_box_container = QtWidgets.QVBoxLayout()
-        self.pb_label = QtWidgets.QLabel("<b>%s</b>" % _('Point/Box Reference:'))
+        self.pb_label = QtWidgets.QLabel("<b>%s:</b>" % _('Point/Box Reference'))
         self.pb_label.setToolTip(
         self.pb_label.setToolTip(
             _("If 'Point' is selected above it store the coordinates (x, y) through which\n"
             _("If 'Point' is selected above it store the coordinates (x, y) through which\n"
               "the mirroring axis passes.\n"
               "the mirroring axis passes.\n"
@@ -189,7 +189,7 @@ class DblSidedTool(FlatCAMTool):
         self.box_combo_type.hide()
         self.box_combo_type.hide()
 
 
         # ## Alignment holes
         # ## Alignment holes
-        self.ah_label = QtWidgets.QLabel("<b>%s</b>" % _('Alignment Drill Coordinates:'))
+        self.ah_label = QtWidgets.QLabel("<b>%s:</b>" % _('Alignment Drill Coordinates'))
         self.ah_label.setToolTip(
         self.ah_label.setToolTip(
            _("Alignment holes (x1, y1), (x2, y2), ... "
            _("Alignment holes (x1, y1), (x2, y2), ... "
              "on one side of the mirror axis. For each set of (x, y) coordinates\n"
              "on one side of the mirror axis. For each set of (x, y) coordinates\n"
@@ -220,7 +220,7 @@ class DblSidedTool(FlatCAMTool):
         grid_lay3.addWidget(self.add_drill_point_button, 0, 1)
         grid_lay3.addWidget(self.add_drill_point_button, 0, 1)
 
 
         # ## Drill diameter for alignment holes
         # ## Drill diameter for alignment holes
-        self.dt_label = QtWidgets.QLabel("<b>%s</b>:" % _('Alignment Drill Diameter'))
+        self.dt_label = QtWidgets.QLabel("<b>%s:</b>" % _('Alignment Drill Diameter'))
         self.dt_label.setToolTip(
         self.dt_label.setToolTip(
             _("Diameter of the drill for the "
             _("Diameter of the drill for the "
               "alignment holes.")
               "alignment holes.")
@@ -231,7 +231,7 @@ class DblSidedTool(FlatCAMTool):
         self.layout.addLayout(hlay)
         self.layout.addLayout(hlay)
 
 
         self.drill_dia = FCEntry()
         self.drill_dia = FCEntry()
-        self.dd_label = QtWidgets.QLabel(_("Drill diam.:"))
+        self.dd_label = QtWidgets.QLabel('%s:' % _("Drill dia"))
         self.dd_label.setToolTip(
         self.dd_label.setToolTip(
             _("Diameter of the drill for the "
             _("Diameter of the drill for the "
               "alignment holes.")
               "alignment holes.")
@@ -288,7 +288,12 @@ class DblSidedTool(FlatCAMTool):
             else:
             else:
                 try:
                 try:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
-                        self.app.ui.splitter.setSizes([0, 1])
+                        # if tab is populated with the tool but it does not have the focus, focus on it
+                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
+                            # focus on Tool Tab
+                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
+                        else:
+                            self.app.ui.splitter.setSizes([0, 1])
                 except AttributeError:
                 except AttributeError:
                     pass
                     pass
         else:
         else:

+ 11 - 6
flatcamTools/ToolFilm.py

@@ -53,7 +53,7 @@ class Film(FlatCAMTool):
         self.tf_type_obj_combo.setItemIcon(0, QtGui.QIcon("share/flatcam_icon16.png"))
         self.tf_type_obj_combo.setItemIcon(0, QtGui.QIcon("share/flatcam_icon16.png"))
         self.tf_type_obj_combo.setItemIcon(2, QtGui.QIcon("share/geometry16.png"))
         self.tf_type_obj_combo.setItemIcon(2, QtGui.QIcon("share/geometry16.png"))
 
 
-        self.tf_type_obj_combo_label = QtWidgets.QLabel(_("Object Type:"))
+        self.tf_type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Object Type"))
         self.tf_type_obj_combo_label.setToolTip(
         self.tf_type_obj_combo_label.setToolTip(
             _("Specify the type of object for which to create the film.\n"
             _("Specify the type of object for which to create the film.\n"
               "The object can be of type: Gerber or Geometry.\n"
               "The object can be of type: Gerber or Geometry.\n"
@@ -68,7 +68,7 @@ class Film(FlatCAMTool):
         self.tf_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.tf_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.tf_object_combo.setCurrentIndex(1)
         self.tf_object_combo.setCurrentIndex(1)
 
 
-        self.tf_object_label = QtWidgets.QLabel(_("Film Object:"))
+        self.tf_object_label = QtWidgets.QLabel('%s:' % _("Film Object"))
         self.tf_object_label.setToolTip(
         self.tf_object_label.setToolTip(
             _("Object for which to create the film.")
             _("Object for which to create the film.")
         )
         )
@@ -101,7 +101,7 @@ class Film(FlatCAMTool):
         self.tf_box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.tf_box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.tf_box_combo.setCurrentIndex(1)
         self.tf_box_combo.setCurrentIndex(1)
 
 
-        self.tf_box_combo_label = QtWidgets.QLabel(_("Box Object:"))
+        self.tf_box_combo_label = QtWidgets.QLabel('%s:' % _("Box Object"))
         self.tf_box_combo_label.setToolTip(
         self.tf_box_combo_label.setToolTip(
             _("The actual object that is used a container for the\n "
             _("The actual object that is used a container for the\n "
               "selected object for which we create the film.\n"
               "selected object for which we create the film.\n"
@@ -127,7 +127,7 @@ class Film(FlatCAMTool):
         # Boundary for negative film generation
         # Boundary for negative film generation
 
 
         self.boundary_entry = FCEntry()
         self.boundary_entry = FCEntry()
-        self.boundary_label = QtWidgets.QLabel(_("Border:"))
+        self.boundary_label = QtWidgets.QLabel('%s:' % _("Border"))
         self.boundary_label.setToolTip(
         self.boundary_label.setToolTip(
             _("Specify a border around the object.\n"
             _("Specify a border around the object.\n"
               "Only for negative film.\n"
               "Only for negative film.\n"
@@ -141,7 +141,7 @@ class Film(FlatCAMTool):
         tf_form_layout.addRow(self.boundary_label, self.boundary_entry)
         tf_form_layout.addRow(self.boundary_label, self.boundary_entry)
 
 
         self.film_scale_entry = FCEntry()
         self.film_scale_entry = FCEntry()
-        self.film_scale_label = QtWidgets.QLabel(_("Scale Stroke:"))
+        self.film_scale_label = QtWidgets.QLabel('%s:' % _("Scale Stroke"))
         self.film_scale_label.setToolTip(
         self.film_scale_label.setToolTip(
             _("Scale the line stroke thickness of each feature in the SVG file.\n"
             _("Scale the line stroke thickness of each feature in the SVG file.\n"
               "It means that the line that envelope each SVG feature will be thicker or thinner,\n"
               "It means that the line that envelope each SVG feature will be thicker or thinner,\n"
@@ -190,7 +190,12 @@ class Film(FlatCAMTool):
             else:
             else:
                 try:
                 try:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
-                        self.app.ui.splitter.setSizes([0, 1])
+                        # if tab is populated with the tool but it does not have the focus, focus on it
+                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
+                            # focus on Tool Tab
+                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
+                        else:
+                            self.app.ui.splitter.setSizes([0, 1])
                 except AttributeError:
                 except AttributeError:
                     pass
                     pass
         else:
         else:

+ 9 - 4
flatcamTools/ToolImage.py

@@ -50,7 +50,7 @@ class ToolImage(FlatCAMTool):
         self.tf_type_obj_combo.setItemIcon(0, QtGui.QIcon("share/flatcam_icon16.png"))
         self.tf_type_obj_combo.setItemIcon(0, QtGui.QIcon("share/flatcam_icon16.png"))
         self.tf_type_obj_combo.setItemIcon(1, QtGui.QIcon("share/geometry16.png"))
         self.tf_type_obj_combo.setItemIcon(1, QtGui.QIcon("share/geometry16.png"))
 
 
-        self.tf_type_obj_combo_label = QtWidgets.QLabel(_("Object Type:"))
+        self.tf_type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Object Type"))
         self.tf_type_obj_combo_label.setToolTip(
         self.tf_type_obj_combo_label.setToolTip(
            _("Specify the type of object to create from the image.\n"
            _("Specify the type of object to create from the image.\n"
              "It can be of type: Gerber or Geometry.")
              "It can be of type: Gerber or Geometry.")
@@ -60,7 +60,7 @@ class ToolImage(FlatCAMTool):
 
 
         # DPI value of the imported image
         # DPI value of the imported image
         self.dpi_entry = IntEntry()
         self.dpi_entry = IntEntry()
-        self.dpi_label = QtWidgets.QLabel(_("DPI value:"))
+        self.dpi_label = QtWidgets.QLabel('%s:' % _("DPI value"))
         self.dpi_label.setToolTip(
         self.dpi_label.setToolTip(
            _("Specify a DPI value for the image.")
            _("Specify a DPI value for the image.")
         )
         )
@@ -69,7 +69,7 @@ class ToolImage(FlatCAMTool):
         self.emty_lbl = QtWidgets.QLabel("")
         self.emty_lbl = QtWidgets.QLabel("")
         self.layout.addWidget(self.emty_lbl)
         self.layout.addWidget(self.emty_lbl)
 
 
-        self.detail_label = QtWidgets.QLabel("<font size=4><b>%s:</b>" % _('Level of detail'))
+        self.detail_label = QtWidgets.QLabel("<font size=4><b>%s:</b></font>" % _('Level of detail'))
         self.layout.addWidget(self.detail_label)
         self.layout.addWidget(self.detail_label)
 
 
         ti2_form_layout = QtWidgets.QFormLayout()
         ti2_form_layout = QtWidgets.QFormLayout()
@@ -157,7 +157,12 @@ class ToolImage(FlatCAMTool):
             else:
             else:
                 try:
                 try:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
-                        self.app.ui.splitter.setSizes([0, 1])
+                        # if tab is populated with the tool but it does not have the focus, focus on it
+                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
+                            # focus on Tool Tab
+                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
+                        else:
+                            self.app.ui.splitter.setSizes([0, 1])
                 except AttributeError:
                 except AttributeError:
                     pass
                     pass
         else:
         else:

+ 3 - 3
flatcamTools/ToolMeasurement.py

@@ -40,7 +40,7 @@ class Measurement(FlatCAMTool):
         form_layout = QtWidgets.QFormLayout()
         form_layout = QtWidgets.QFormLayout()
         self.layout.addLayout(form_layout)
         self.layout.addLayout(form_layout)
 
 
-        self.units_label = QtWidgets.QLabel(_("Units:"))
+        self.units_label = QtWidgets.QLabel('%s:' % _("Units"))
         self.units_label.setToolTip(_("Those are the units in which the distance is measured."))
         self.units_label.setToolTip(_("Those are the units in which the distance is measured."))
         self.units_value = QtWidgets.QLabel("%s" % str({'mm': _("METRIC (mm)"), 'in': _("INCH (in)")}[self.units]))
         self.units_value = QtWidgets.QLabel("%s" % str({'mm': _("METRIC (mm)"), 'in': _("INCH (in)")}[self.units]))
         self.units_value.setDisabled(True)
         self.units_value.setDisabled(True)
@@ -51,10 +51,10 @@ class Measurement(FlatCAMTool):
         self.stop_label = QtWidgets.QLabel("<b>%s</b> %s:" % (_('Stop'), _('Coords')))
         self.stop_label = QtWidgets.QLabel("<b>%s</b> %s:" % (_('Stop'), _('Coords')))
         self.stop_label.setToolTip(_("This is the measuring Stop point coordinates."))
         self.stop_label.setToolTip(_("This is the measuring Stop point coordinates."))
 
 
-        self.distance_x_label = QtWidgets.QLabel(_("Dx:"))
+        self.distance_x_label = QtWidgets.QLabel('%s:' % _("Dx"))
         self.distance_x_label.setToolTip(_("This is the distance measured over the X axis."))
         self.distance_x_label.setToolTip(_("This is the distance measured over the X axis."))
 
 
-        self.distance_y_label = QtWidgets.QLabel(_("Dy:"))
+        self.distance_y_label = QtWidgets.QLabel('%s:' % _("Dy"))
         self.distance_y_label.setToolTip(_("This is the distance measured over the Y axis."))
         self.distance_y_label.setToolTip(_("This is the distance measured over the Y axis."))
 
 
         self.total_distance_label = QtWidgets.QLabel("<b>%s:</b>" % _('DISTANCE'))
         self.total_distance_label = QtWidgets.QLabel("<b>%s:</b>" % _('DISTANCE'))

+ 312 - 92
flatcamTools/ToolNonCopperClear.py

@@ -10,10 +10,10 @@ from FlatCAMTool import FlatCAMTool
 from copy import copy, deepcopy
 from copy import copy, deepcopy
 from ObjectCollection import *
 from ObjectCollection import *
 import time
 import time
+from shapely.geometry import base
 
 
 import gettext
 import gettext
 import FlatCAMTranslation as fcTranslate
 import FlatCAMTranslation as fcTranslate
-from shapely.geometry import base
 import builtins
 import builtins
 
 
 fcTranslate.apply_language('strings')
 fcTranslate.apply_language('strings')
@@ -53,22 +53,46 @@ class NonCopperClear(FlatCAMTool, Gerber):
         form_layout = QtWidgets.QFormLayout()
         form_layout = QtWidgets.QFormLayout()
         self.tools_box.addLayout(form_layout)
         self.tools_box.addLayout(form_layout)
 
 
-        # ## Object
-        self.object_combo = QtWidgets.QComboBox()
-        self.object_combo.setModel(self.app.collection)
-        self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.object_combo.setCurrentIndex(1)
-
-        self.object_label = QtWidgets.QLabel("Gerber:")
-        self.object_label.setToolTip(
-            _("Gerber object to be cleared of excess copper.                        ")
+        # ################################################
+        # ##### Type of object to be copper cleaned ######
+        # ################################################
+        self.type_obj_combo = QtWidgets.QComboBox()
+        self.type_obj_combo.addItem("Gerber")
+        self.type_obj_combo.addItem("Excellon")
+        self.type_obj_combo.addItem("Geometry")
+
+        # we get rid of item1 ("Excellon") as it is not suitable
+        self.type_obj_combo.view().setRowHidden(1, True)
+        self.type_obj_combo.setItemIcon(0, QtGui.QIcon("share/flatcam_icon16.png"))
+        self.type_obj_combo.setItemIcon(2, QtGui.QIcon("share/geometry16.png"))
+
+        self.type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Obj Type"))
+        self.type_obj_combo_label.setToolTip(
+            _("Specify the type of object to be cleared of excess copper.\n"
+              "It can be of type: Gerber or Geometry.\n"
+              "What is selected here will dictate the kind\n"
+              "of objects that will populate the 'Object' combobox.")
         )
         )
-        e_lab_0 = QtWidgets.QLabel('')
+        self.type_obj_combo_label.setMinimumWidth(60)
+        form_layout.addRow(self.type_obj_combo_label, self.type_obj_combo)
+
+        # ################################################
+        # ##### The object to be copper cleaned ##########
+        # ################################################
+        self.obj_combo = QtWidgets.QComboBox()
+        self.obj_combo.setModel(self.app.collection)
+        self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.obj_combo.setCurrentIndex(1)
+
+        self.object_label = QtWidgets.QLabel('%s:' % _("Object"))
+        self.object_label.setToolTip(_("Object to be cleared of excess copper."))
 
 
-        form_layout.addRow(self.object_label, self.object_combo)
+        form_layout.addRow(self.object_label, self.obj_combo)
+
+        e_lab_0 = QtWidgets.QLabel('')
         form_layout.addRow(e_lab_0)
         form_layout.addRow(e_lab_0)
 
 
-        #### Tools ## ##
+        # ### Tools ## ##
         self.tools_table_label = QtWidgets.QLabel('<b>%s</b>' % _('Tools Table'))
         self.tools_table_label = QtWidgets.QLabel('<b>%s</b>' % _('Tools Table'))
         self.tools_table_label.setToolTip(
         self.tools_table_label.setToolTip(
             _("Tools pool from which the algorithm\n"
             _("Tools pool from which the algorithm\n"
@@ -110,23 +134,35 @@ class NonCopperClear(FlatCAMTool, Gerber):
               "Choosing the <B>V-Shape</B> Tool Type automatically will select the Operation Type "
               "Choosing the <B>V-Shape</B> Tool Type automatically will select the Operation Type "
               "in the resulting geometry as Isolation."))
               "in the resulting geometry as Isolation."))
 
 
-        self.empty_label = QtWidgets.QLabel('')
-        self.tools_box.addWidget(self.empty_label)
+        self.ncc_order_label = QtWidgets.QLabel('<b>%s:</b>' % _('Tool order'))
+        self.ncc_order_label.setToolTip(_("This set the way that the tools in the tools table are used.\n"
+                                          "'No' --> means that the used order is the one in the tool table\n"
+                                          "'Forward' --> means that the tools will be ordered from small to big\n"
+                                          "'Reverse' --> menas that the tools will ordered from big to small\n\n"
+                                          "WARNING: using rest machining will automatically set the order\n"
+                                          "in reverse and disable this control."))
+
+        self.ncc_order_radio = RadioSet([{'label': _('No'), 'value': 'no'},
+                                         {'label': _('Forward'), 'value': 'fwd'},
+                                         {'label': _('Reverse'), 'value': 'rev'}])
+        self.ncc_order_radio.setToolTip(_("This set the way that the tools in the tools table are used.\n"
+                                          "'No' --> means that the used order is the one in the tool table\n"
+                                          "'Forward' --> means that the tools will be ordered from small to big\n"
+                                          "'Reverse' --> menas that the tools will ordered from big to small\n\n"
+                                          "WARNING: using rest machining will automatically set the order\n"
+                                          "in reverse and disable this control."))
+        form = QtWidgets.QFormLayout()
+        self.tools_box.addLayout(form)
+        form.addRow(QtWidgets.QLabel(''), QtWidgets.QLabel(''))
+        form.addRow(self.ncc_order_label, self.ncc_order_radio)
 
 
         # ### Add a new Tool ####
         # ### Add a new Tool ####
-        hlay = QtWidgets.QHBoxLayout()
-        self.tools_box.addLayout(hlay)
-
         self.addtool_entry_lbl = QtWidgets.QLabel('<b>%s:</b>' % _('Tool Dia'))
         self.addtool_entry_lbl = QtWidgets.QLabel('<b>%s:</b>' % _('Tool Dia'))
         self.addtool_entry_lbl.setToolTip(
         self.addtool_entry_lbl.setToolTip(
             _("Diameter for the new tool to add in the Tool Table")
             _("Diameter for the new tool to add in the Tool Table")
         )
         )
         self.addtool_entry = FCEntry2()
         self.addtool_entry = FCEntry2()
-
-        # hlay.addWidget(self.addtool_label)
-        # hlay.addStretch()
-        hlay.addWidget(self.addtool_entry_lbl)
-        hlay.addWidget(self.addtool_entry)
+        form.addRow(self.addtool_entry_lbl, self.addtool_entry)
 
 
         grid2 = QtWidgets.QGridLayout()
         grid2 = QtWidgets.QGridLayout()
         self.tools_box.addLayout(grid2)
         self.tools_box.addLayout(grid2)
@@ -159,7 +195,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
         grid3 = QtWidgets.QGridLayout()
         grid3 = QtWidgets.QGridLayout()
         self.tools_box.addLayout(grid3)
         self.tools_box.addLayout(grid3)
 
 
-        e_lab_1 = QtWidgets.QLabel('')
+        e_lab_1 = QtWidgets.QLabel('<b>%s:</b>' % _("Parameters"))
         grid3.addWidget(e_lab_1, 0, 0)
         grid3.addWidget(e_lab_1, 0, 0)
 
 
         nccoverlabel = QtWidgets.QLabel(_('Overlap Rate:'))
         nccoverlabel = QtWidgets.QLabel(_('Overlap Rate:'))
@@ -178,7 +214,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.ncc_overlap_entry = FCEntry()
         self.ncc_overlap_entry = FCEntry()
         grid3.addWidget(self.ncc_overlap_entry, 1, 1)
         grid3.addWidget(self.ncc_overlap_entry, 1, 1)
 
 
-        nccmarginlabel = QtWidgets.QLabel(_('Margin:'))
+        nccmarginlabel = QtWidgets.QLabel('%s:' % _('Margin'))
         nccmarginlabel.setToolTip(
         nccmarginlabel.setToolTip(
             _("Bounding box margin.")
             _("Bounding box margin.")
         )
         )
@@ -187,7 +223,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
         grid3.addWidget(self.ncc_margin_entry, 2, 1)
         grid3.addWidget(self.ncc_margin_entry, 2, 1)
 
 
         # Method
         # Method
-        methodlabel = QtWidgets.QLabel(_('Method:'))
+        methodlabel = QtWidgets.QLabel('%s:' % _('Method'))
         methodlabel.setToolTip(
         methodlabel.setToolTip(
             _("Algorithm for non-copper clearing:<BR>"
             _("Algorithm for non-copper clearing:<BR>"
               "<B>Standard</B>: Fixed step inwards.<BR>"
               "<B>Standard</B>: Fixed step inwards.<BR>"
@@ -203,7 +239,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
         grid3.addWidget(self.ncc_method_radio, 3, 1)
         grid3.addWidget(self.ncc_method_radio, 3, 1)
 
 
         # Connect lines
         # Connect lines
-        pathconnectlabel = QtWidgets.QLabel(_("Connect:"))
+        pathconnectlabel = QtWidgets.QLabel('%s:' % _("Connect"))
         pathconnectlabel.setToolTip(
         pathconnectlabel.setToolTip(
             _("Draw lines between resulting\n"
             _("Draw lines between resulting\n"
               "segments to minimize tool lifts.")
               "segments to minimize tool lifts.")
@@ -212,7 +248,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.ncc_connect_cb = FCCheckBox()
         self.ncc_connect_cb = FCCheckBox()
         grid3.addWidget(self.ncc_connect_cb, 4, 1)
         grid3.addWidget(self.ncc_connect_cb, 4, 1)
 
 
-        contourlabel = QtWidgets.QLabel(_("Contour:"))
+        contourlabel = QtWidgets.QLabel('%s:' % _("Contour"))
         contourlabel.setToolTip(
         contourlabel.setToolTip(
             _("Cut around the perimeter of the polygon\n"
             _("Cut around the perimeter of the polygon\n"
               "to trim rough edges.")
               "to trim rough edges.")
@@ -221,7 +257,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.ncc_contour_cb = FCCheckBox()
         self.ncc_contour_cb = FCCheckBox()
         grid3.addWidget(self.ncc_contour_cb, 5, 1)
         grid3.addWidget(self.ncc_contour_cb, 5, 1)
 
 
-        restlabel = QtWidgets.QLabel(_("Rest M.:"))
+        restlabel = QtWidgets.QLabel('%s:' % _("Rest M."))
         restlabel.setToolTip(
         restlabel.setToolTip(
             _("If checked, use 'rest machining'.\n"
             _("If checked, use 'rest machining'.\n"
               "Basically it will clear copper outside PCB features,\n"
               "Basically it will clear copper outside PCB features,\n"
@@ -236,7 +272,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
         grid3.addWidget(self.ncc_rest_cb, 6, 1)
         grid3.addWidget(self.ncc_rest_cb, 6, 1)
 
 
         # ## NCC Offset choice
         # ## NCC Offset choice
-        self.ncc_offset_choice_label = QtWidgets.QLabel(_("Offset:"))
+        self.ncc_offset_choice_label = QtWidgets.QLabel('%s:' % _("Offset"))
         self.ncc_offset_choice_label.setToolTip(
         self.ncc_offset_choice_label.setToolTip(
             _("If used, it will add an offset to the copper features.\n"
             _("If used, it will add an offset to the copper features.\n"
               "The copper clearing will finish to a distance\n"
               "The copper clearing will finish to a distance\n"
@@ -248,7 +284,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
         grid3.addWidget(self.ncc_choice_offset_cb, 7, 1)
         grid3.addWidget(self.ncc_choice_offset_cb, 7, 1)
 
 
         # ## NCC Offset value
         # ## NCC Offset value
-        self.ncc_offset_label = QtWidgets.QLabel(_("Offset value:"))
+        self.ncc_offset_label = QtWidgets.QLabel('%s:' % _("Offset value"))
         self.ncc_offset_label.setToolTip(
         self.ncc_offset_label.setToolTip(
             _("If used, it will add an offset to the copper features.\n"
             _("If used, it will add an offset to the copper features.\n"
               "The copper clearing will finish to a distance\n"
               "The copper clearing will finish to a distance\n"
@@ -273,22 +309,27 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.ncc_offset_spinner.hide()
         self.ncc_offset_spinner.hide()
 
 
         # ## Reference
         # ## Reference
-        self.reference_radio = RadioSet([{'label': _('Itself'), 'value': 'itself'},
-                                         {'label': _('Box'), 'value': 'box'}])
+        self.reference_radio = RadioSet([
+            {'label': _('Itself'), 'value': 'itself'},
+            {"label": _("Area Selection"), "value": "area"},
+            {'label':  _("Reference Object"), 'value': 'box'}
+        ], orientation='vertical', stretch=False)
         self.reference_label = QtWidgets.QLabel(_("Reference:"))
         self.reference_label = QtWidgets.QLabel(_("Reference:"))
         self.reference_label.setToolTip(
         self.reference_label.setToolTip(
-            _("- 'Itself': the non copper clearing extent\n"
+            _("- 'Itself' -  the non copper clearing extent\n"
               "is based on the object that is copper cleared.\n "
               "is based on the object that is copper cleared.\n "
-              "- 'Box': will do non copper clearing within the box\n"
-              "specified by the object selected in the Ref. Object combobox.")
+              "- 'Area Selection' - left mouse click to start selection of the area to be painted.\n"
+              "Keeping a modifier key pressed (CTRL or SHIFT) will allow to add multiple areas.\n"
+              "- 'Reference Object' -  will do non copper clearing within the area\n"
+              "specified by another object.")
         )
         )
         grid3.addWidget(self.reference_label, 9, 0)
         grid3.addWidget(self.reference_label, 9, 0)
         grid3.addWidget(self.reference_radio, 9, 1)
         grid3.addWidget(self.reference_radio, 9, 1)
 
 
-        grid4 = QtWidgets.QGridLayout()
-        self.tools_box.addLayout(grid4)
+        form1 = QtWidgets.QFormLayout()
+        self.tools_box.addLayout(form1)
 
 
-        self.box_combo_type_label = QtWidgets.QLabel(_("Ref. Type:"))
+        self.box_combo_type_label = QtWidgets.QLabel('%s:' % _("Ref. Type"))
         self.box_combo_type_label.setToolTip(
         self.box_combo_type_label.setToolTip(
             _("The type of FlatCAM object to be used as non copper clearing reference.\n"
             _("The type of FlatCAM object to be used as non copper clearing reference.\n"
               "It can be Gerber, Excellon or Geometry.")
               "It can be Gerber, Excellon or Geometry.")
@@ -297,11 +338,9 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.box_combo_type.addItem(_("Gerber   Reference Box Object"))
         self.box_combo_type.addItem(_("Gerber   Reference Box Object"))
         self.box_combo_type.addItem(_("Excellon Reference Box Object"))
         self.box_combo_type.addItem(_("Excellon Reference Box Object"))
         self.box_combo_type.addItem(_("Geometry Reference Box Object"))
         self.box_combo_type.addItem(_("Geometry Reference Box Object"))
+        form1.addRow(self.box_combo_type_label, self.box_combo_type)
 
 
-        grid4.addWidget(self.box_combo_type_label, 0, 0)
-        grid4.addWidget(self.box_combo_type, 0, 1)
-
-        self.box_combo_label = QtWidgets.QLabel(_("Ref. Object:"))
+        self.box_combo_label = QtWidgets.QLabel('%s:' % _("Ref. Object"))
         self.box_combo_label.setToolTip(
         self.box_combo_label.setToolTip(
             _("The FlatCAM object to be used as non copper clearing reference.")
             _("The FlatCAM object to be used as non copper clearing reference.")
         )
         )
@@ -309,8 +348,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.box_combo.setModel(self.app.collection)
         self.box_combo.setModel(self.app.collection)
         self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.box_combo.setCurrentIndex(1)
         self.box_combo.setCurrentIndex(1)
-        grid4.addWidget(self.box_combo_label, 1, 0)
-        grid4.addWidget(self.box_combo, 1, 1)
+        form1.addRow(self.box_combo_label, self.box_combo)
 
 
         self.box_combo.hide()
         self.box_combo.hide()
         self.box_combo_label.hide()
         self.box_combo_label.hide()
@@ -323,6 +361,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
               "for non-copper routing.")
               "for non-copper routing.")
         )
         )
         self.tools_box.addWidget(self.generate_ncc_button)
         self.tools_box.addWidget(self.generate_ncc_button)
+        self.tools_box.addStretch()
 
 
         self.units = ''
         self.units = ''
         self.ncc_tools = {}
         self.ncc_tools = {}
@@ -333,19 +372,32 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.obj_name = ""
         self.obj_name = ""
         self.ncc_obj = None
         self.ncc_obj = None
 
 
+        self.sel_rect = []
+
         self.bound_obj_name = ""
         self.bound_obj_name = ""
         self.bound_obj = None
         self.bound_obj = None
 
 
-        self.tools_box.addStretch()
+        self.first_click = False
+        self.cursor_pos = None
+        self.mouse_is_dragging = False
 
 
         self.addtool_btn.clicked.connect(self.on_tool_add)
         self.addtool_btn.clicked.connect(self.on_tool_add)
         self.addtool_entry.returnPressed.connect(self.on_tool_add)
         self.addtool_entry.returnPressed.connect(self.on_tool_add)
         self.deltool_btn.clicked.connect(self.on_tool_delete)
         self.deltool_btn.clicked.connect(self.on_tool_delete)
-        self.generate_ncc_button.clicked.connect(self.on_ncc)
+        self.generate_ncc_button.clicked.connect(self.on_ncc_click)
 
 
         self.box_combo_type.currentIndexChanged.connect(self.on_combo_box_type)
         self.box_combo_type.currentIndexChanged.connect(self.on_combo_box_type)
         self.reference_radio.group_toggle_fn = self.on_toggle_reference
         self.reference_radio.group_toggle_fn = self.on_toggle_reference
         self.ncc_choice_offset_cb.stateChanged.connect(self.on_offset_choice)
         self.ncc_choice_offset_cb.stateChanged.connect(self.on_offset_choice)
+        self.ncc_rest_cb.stateChanged.connect(self.on_rest_machining_check)
+        self.ncc_order_radio.activated_custom[str].connect(self.on_order_changed)
+
+        self.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
+
+    def on_type_obj_index_changed(self, index):
+        obj_type = self.type_obj_combo.currentIndex()
+        self.obj_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
+        self.obj_combo.setCurrentIndex(0)
 
 
     def install(self, icon=None, separator=None, **kwargs):
     def install(self, icon=None, separator=None, **kwargs):
         FlatCAMTool.install(self, icon, separator, shortcut='ALT+N', **kwargs)
         FlatCAMTool.install(self, icon, separator, shortcut='ALT+N', **kwargs)
@@ -360,7 +412,12 @@ class NonCopperClear(FlatCAMTool, Gerber):
             else:
             else:
                 try:
                 try:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
-                        self.app.ui.splitter.setSizes([0, 1])
+                        # if tab is populated with the tool but it does not have the focus, focus on it
+                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
+                            # focus on Tool Tab
+                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
+                        else:
+                            self.app.ui.splitter.setSizes([0, 1])
                 except AttributeError:
                 except AttributeError:
                     pass
                     pass
         else:
         else:
@@ -382,6 +439,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
     def set_tool_ui(self):
     def set_tool_ui(self):
         self.tools_frame.show()
         self.tools_frame.show()
 
 
+        self.ncc_order_radio.set_value(self.app.defaults["tools_nccorder"])
         self.ncc_overlap_entry.set_value(self.app.defaults["tools_nccoverlap"])
         self.ncc_overlap_entry.set_value(self.app.defaults["tools_nccoverlap"])
         self.ncc_margin_entry.set_value(self.app.defaults["tools_nccmargin"])
         self.ncc_margin_entry.set_value(self.app.defaults["tools_nccmargin"])
         self.ncc_method_radio.set_value(self.app.defaults["tools_nccmethod"])
         self.ncc_method_radio.set_value(self.app.defaults["tools_nccmethod"])
@@ -484,7 +542,14 @@ class NonCopperClear(FlatCAMTool, Gerber):
         sorted_tools = []
         sorted_tools = []
         for k, v in self.ncc_tools.items():
         for k, v in self.ncc_tools.items():
             sorted_tools.append(float('%.4f' % float(v['tooldia'])))
             sorted_tools.append(float('%.4f' % float(v['tooldia'])))
-        sorted_tools.sort()
+
+        order = self.ncc_order_radio.get_value()
+        if order == 'fwd':
+            sorted_tools.sort(reverse=False)
+        elif order == 'rev':
+            sorted_tools.sort(reverse=True)
+        else:
+            pass
 
 
         n = len(sorted_tools)
         n = len(sorted_tools)
         self.tools_table.setRowCount(n)
         self.tools_table.setRowCount(n)
@@ -570,7 +635,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.box_combo.setCurrentIndex(0)
         self.box_combo.setCurrentIndex(0)
 
 
     def on_toggle_reference(self):
     def on_toggle_reference(self):
-        if self.reference_radio.get_value() == "itself":
+        if self.reference_radio.get_value() == "itself" or self.reference_radio.get_value() == "area":
             self.box_combo.hide()
             self.box_combo.hide()
             self.box_combo_label.hide()
             self.box_combo_label.hide()
             self.box_combo_type.hide()
             self.box_combo_type.hide()
@@ -589,6 +654,19 @@ class NonCopperClear(FlatCAMTool, Gerber):
             self.ncc_offset_label.hide()
             self.ncc_offset_label.hide()
             self.ncc_offset_spinner.hide()
             self.ncc_offset_spinner.hide()
 
 
+    def on_order_changed(self, order):
+        if order != 'no':
+            self.build_ui()
+
+    def on_rest_machining_check(self, state):
+        if state:
+            self.ncc_order_radio.set_value('rev')
+            self.ncc_order_label.setDisabled(True)
+            self.ncc_order_radio.setDisabled(True)
+        else:
+            self.ncc_order_label.setDisabled(False)
+            self.ncc_order_radio.setDisabled(False)
+
     def on_tool_add(self, dia=None, muted=None):
     def on_tool_add(self, dia=None, muted=None):
 
 
         self.ui_disconnect()
         self.ui_disconnect()
@@ -743,10 +821,128 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.app.inform.emit(_("[success] Tool(s) deleted from Tool Table."))
         self.app.inform.emit(_("[success] Tool(s) deleted from Tool Table."))
         self.build_ui()
         self.build_ui()
 
 
-    def on_ncc(self):
+    def on_ncc_click(self):
         self.bound_obj = None
         self.bound_obj = None
         self.ncc_obj = None
         self.ncc_obj = None
 
 
+        ref_choice = self.reference_radio.get_value()
+
+        if ref_choice == 'itself':
+            self.bound_obj_name = self.object_combo.currentText()
+            # Get source object.
+            try:
+                self.bound_obj = self.app.collection.get_by_name(self.bound_obj_name)
+            except Exception as e:
+                self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % self.obj_name)
+                return "Could not retrieve object: %s" % self.obj_name
+            self.on_ncc()
+        elif ref_choice == 'box':
+            self.bound_obj_name = self.box_combo.currentText()
+            # Get source object.
+            try:
+                self.bound_obj = self.app.collection.get_by_name(self.bound_obj_name)
+            except Exception as e:
+                self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % self.bound_obj_name)
+                return "Could not retrieve object: %s. Error: %s" % (self.bound_obj_name, str(e))
+            self.on_ncc()
+        else:
+            self.app.inform.emit(_("[WARNING_NOTCL] Click the start point of the area."))
+
+            # use the first tool in the tool table; get the diameter
+            tooldia = float('%.4f' % float(self.tools_table.item(0, 1).text()))
+
+            # To be called after clicking on the plot.
+            def on_mouse_release(event):
+                # do paint single only for left mouse clicks
+                if event.button == 1:
+                    if self.first_click is False:
+                        self.first_click = True
+                        self.app.inform.emit(_("[WARNING_NOTCL] Click the end point of the paint area."))
+
+                        self.cursor_pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
+                        if self.app.grid_status() == True:
+                            self.cursor_pos = self.app.geo_editor.snap(self.cursor_pos[0], self.cursor_pos[1])
+                    else:
+                        self.app.inform.emit(_("Zone added. Right click to finish."))
+                        self.app.delete_selection_shape()
+
+                        curr_pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
+                        if self.app.grid_status() == True:
+                            curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
+
+                        x0, y0 = self.cursor_pos[0], self.cursor_pos[1]
+                        x1, y1 = curr_pos[0], curr_pos[1]
+                        pt1 = (x0, y0)
+                        pt2 = (x1, y0)
+                        pt3 = (x1, y1)
+                        pt4 = (x0, y1)
+                        self.sel_rect.append(Polygon([pt1, pt2, pt3, pt4]))
+
+                        modifiers = QtWidgets.QApplication.keyboardModifiers()
+
+                        if modifiers == QtCore.Qt.ShiftModifier:
+                            mod_key = 'Shift'
+                        elif modifiers == QtCore.Qt.ControlModifier:
+                            mod_key = 'Control'
+                        else:
+                            mod_key = None
+
+                        if mod_key == self.app.defaults["global_mselect_key"]:
+                            self.first_click = False
+                            return
+
+                        self.app.plotcanvas.vis_disconnect('mouse_release', on_mouse_release)
+                        self.app.plotcanvas.vis_disconnect('mouse_move', on_mouse_move)
+
+                        self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
+                        self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
+                        self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
+
+                        self.on_ncc()
+                elif event.button == 2 and self.first_click is False and self.mouse_is_dragging is False:
+                    self.first_click = False
+                    self.app.plotcanvas.vis_disconnect('mouse_release', on_mouse_release)
+                    self.app.plotcanvas.vis_disconnect('mouse_move', on_mouse_move)
+
+                    self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
+                    self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
+                    self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
+
+                    self.on_ncc()
+
+            # called on mouse move
+            def on_mouse_move(event):
+                curr_pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
+                self.app.app_cursor.enabled = False
+
+                if event.button == 2:
+                    if event.is_dragging is True:
+                        self.mouse_is_dragging = True
+                    else:
+                        self.mouse_is_dragging = False
+
+                if self.app.grid_status() == True:
+                    self.app.app_cursor.enabled = True
+                    # Update cursor
+                    curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
+                    self.app.app_cursor.set_data(np.asarray([(curr_pos[0], curr_pos[1])]),
+                                                 symbol='++', edge_color='black', size=20)
+
+                if self.first_click:
+                    self.app.delete_selection_shape()
+                    self.app.draw_moving_selection_shape(old_coords=(self.cursor_pos[0], self.cursor_pos[1]),
+                                                         coords=(curr_pos[0], curr_pos[1]),
+                                                         face_alpha=0.0)
+
+            self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
+            self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
+            self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+
+            self.app.plotcanvas.vis_connect('mouse_release', on_mouse_release)
+            self.app.plotcanvas.vis_connect('mouse_move', on_mouse_move)
+
+    def on_ncc(self):
+
         try:
         try:
             over = float(self.ncc_overlap_entry.get_value())
             over = float(self.ncc_overlap_entry.get_value())
         except ValueError:
         except ValueError:
@@ -797,24 +993,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
         pol_method = self.ncc_method_radio.get_value()
         pol_method = self.ncc_method_radio.get_value()
         pol_method = pol_method if pol_method else self.app.defaults["tools_nccmethod"]
         pol_method = pol_method if pol_method else self.app.defaults["tools_nccmethod"]
 
 
-        if self.reference_radio.get_value() == 'itself':
-            self.bound_obj_name = self.object_combo.currentText()
-            # Get source object.
-            try:
-                self.bound_obj = self.app.collection.get_by_name(self.bound_obj_name)
-            except Exception as e:
-                self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % self.obj_name)
-                return "Could not retrieve object: %s" % self.obj_name
-        else:
-            self.bound_obj_name = self.box_combo.currentText()
-            # Get source object.
-            try:
-                self.bound_obj = self.app.collection.get_by_name(self.bound_obj_name)
-            except Exception as e:
-                self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % self.obj_name)
-                return "Could not retrieve object: %s" % self.obj_name
-
-        self.obj_name = self.object_combo.currentText()
+        self.obj_name = self.obj_combo.currentText()
         # Get source object.
         # Get source object.
         try:
         try:
             self.ncc_obj = self.app.collection.get_by_name(self.obj_name)
             self.ncc_obj = self.app.collection.get_by_name(self.obj_name)
@@ -823,26 +1002,52 @@ class NonCopperClear(FlatCAMTool, Gerber):
             return "Could not retrieve object: %s" % self.obj_name
             return "Could not retrieve object: %s" % self.obj_name
 
 
         # Prepare non-copper polygons
         # Prepare non-copper polygons
-        try:
-            if not isinstance(self.bound_obj.solid_geometry, MultiPolygon):
-                env_obj = cascaded_union(self.bound_obj.solid_geometry)
-                env_obj = env_obj.convex_hull
-            else:
-                env_obj = self.bound_obj.solid_geometry.convex_hull
-            bounding_box = env_obj.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
-        except Exception as e:
-            log.debug("NonCopperClear.on_ncc() --> %s" % str(e))
-            self.app.inform.emit(_("[ERROR_NOTCL] No object available."))
-            return
+        if self.reference_radio.get_value() == 'area':
+            geo_n = self.sel_rect
+
+            geo_buff_list = []
+            for poly in geo_n:
+                geo_buff_list.append(poly.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
+            bounding_box = cascaded_union(geo_buff_list)
+        else:
+            geo_n = self.bound_obj.solid_geometry
+
+            try:
+                if isinstance(geo_n, MultiPolygon):
+                    env_obj = geo_n.convex_hull
+                elif (isinstance(geo_n, MultiPolygon) and len(geo_n) == 1) or \
+                        (isinstance(geo_n, list) and len(geo_n) == 1) and isinstance(geo_n[0], Polygon):
+                    env_obj = cascaded_union(geo_n)
+                else:
+                    env_obj = cascaded_union(geo_n)
+                    env_obj = env_obj.convex_hull
+                bounding_box = env_obj.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
+            except Exception as e:
+                log.debug("NonCopperClear.on_ncc() --> %s" % str(e))
+                self.app.inform.emit(_("[ERROR_NOTCL] No object available."))
+                return
 
 
         # calculate the empty area by subtracting the solid_geometry from the object bounding box geometry
         # calculate the empty area by subtracting the solid_geometry from the object bounding box geometry
-        if self.ncc_choice_offset_cb.isChecked():
-            self.app.inform.emit(_("[WARNING_NOTCL] Buffering ..."))
-            offseted_geo = self.ncc_obj.solid_geometry.buffer(distance=ncc_offset_value)
-            self.app.inform.emit(_("[success] Buffering finished ..."))
-            empty = self.get_ncc_empty_area(target=offseted_geo, boundary=bounding_box)
+        if isinstance(self.ncc_obj, FlatCAMGerber):
+            if self.ncc_choice_offset_cb.isChecked():
+                self.app.inform.emit(_("[WARNING_NOTCL] Buffering ..."))
+                offseted_geo = self.ncc_obj.solid_geometry.buffer(distance=ncc_offset_value)
+                self.app.inform.emit(_("[success] Buffering finished ..."))
+                empty = self.get_ncc_empty_area(target=offseted_geo, boundary=bounding_box)
+            else:
+                empty = self.get_ncc_empty_area(target=self.ncc_obj.solid_geometry, boundary=bounding_box)
+        elif isinstance(self.ncc_obj, FlatCAMGeometry):
+            sol_geo = cascaded_union(self.ncc_obj.solid_geometry)
+            if self.ncc_choice_offset_cb.isChecked():
+                self.app.inform.emit(_("[WARNING_NOTCL] Buffering ..."))
+                offseted_geo = sol_geo.buffer(distance=ncc_offset_value)
+                self.app.inform.emit(_("[success] Buffering finished ..."))
+                empty = self.get_ncc_empty_area(target=offseted_geo, boundary=bounding_box)
+            else:
+                empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box)
         else:
         else:
-            empty = self.get_ncc_empty_area(target=self.ncc_obj.solid_geometry, boundary=bounding_box)
+            self.inform.emit(_('[ERROR_NOTCL] The selected object is not suitable for copper clearing.'))
+            return
 
 
         if type(empty) is Polygon:
         if type(empty) is Polygon:
             empty = MultiPolygon([empty])
             empty = MultiPolygon([empty])
@@ -878,7 +1083,14 @@ class NonCopperClear(FlatCAMTool, Gerber):
         sorted_tools = []
         sorted_tools = []
         for k, v in self.ncc_tools.items():
         for k, v in self.ncc_tools.items():
             sorted_tools.append(float('%.4f' % float(v['tooldia'])))
             sorted_tools.append(float('%.4f' % float(v['tooldia'])))
-        sorted_tools.sort(reverse=True)
+
+        order = self.ncc_order_radio.get_value()
+        if order == 'fwd':
+            sorted_tools.sort(reverse=False)
+        elif order == 'rev':
+            sorted_tools.sort(reverse=True)
+        else:
+            pass
 
 
         # Do job in background
         # Do job in background
         proc = self.app.proc_container.new(_("Clearing Non-Copper areas."))
         proc = self.app.proc_container.new(_("Clearing Non-Copper areas."))
@@ -897,6 +1109,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
             # Generate area for each tool
             # Generate area for each tool
             offset = sum(sorted_tools)
             offset = sum(sorted_tools)
             current_uid = int(1)
             current_uid = int(1)
+            tool = eval(self.app.defaults["tools_ncctools"])[0]
 
 
             for tool in sorted_tools:
             for tool in sorted_tools:
                 self.app.inform.emit(_('[success] Non-Copper Clearing with ToolDia = %s started.') % str(tool))
                 self.app.inform.emit(_('[success] Non-Copper Clearing with ToolDia = %s started.') % str(tool))
@@ -982,8 +1195,6 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
 
             # focus on Selected Tab
             # focus on Selected Tab
             self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
             self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
-            self.tools_frame.hide()
-            self.app.ui.notebook.setTabText(2, _("Tools"))
 
 
         # Promise object with the new name
         # Promise object with the new name
         self.app.collection.promise(name)
         self.app.collection.promise(name)
@@ -1013,6 +1224,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
             cleared_by_last_tool = []
             cleared_by_last_tool = []
             rest_geo = []
             rest_geo = []
             current_uid = 1
             current_uid = 1
+            tool = eval(self.app.defaults["tools_ncctools"])[0]
 
 
             # repurposed flag for final object, geo_obj. True if it has any solid_geometry, False if not.
             # repurposed flag for final object, geo_obj. True if it has any solid_geometry, False if not.
             app_obj.poly_not_cleared = True
             app_obj.poly_not_cleared = True
@@ -1135,9 +1347,6 @@ class NonCopperClear(FlatCAMTool, Gerber):
             # reset the variable for next use
             # reset the variable for next use
             app_obj.poly_not_cleared = False
             app_obj.poly_not_cleared = False
 
 
-            self.tools_frame.hide()
-            app_obj.ui.notebook.setTabText(2, "Tools")
-
         # Promise object with the new name
         # Promise object with the new name
         self.app.collection.promise(name)
         self.app.collection.promise(name)
 
 
@@ -1157,3 +1366,14 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
 
     def reset_fields(self):
     def reset_fields(self):
         self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+
+    def reset_usage(self):
+        self.obj_name = ""
+        self.ncc_obj = None
+        self.bound_obj = None
+
+        self.first_click = False
+        self.cursor_pos = None
+        self.mouse_is_dragging = False
+
+        self.sel_rect = []

+ 289 - 48
flatcamTools/ToolPaint.py

@@ -1,14 +1,15 @@
-# ########################################################## ##
+# ##########################################################
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # http://flatcam.org                                       #
 # http://flatcam.org                                       #
 # File Modified: Marius Adrian Stanciu (c)                 #
 # File Modified: Marius Adrian Stanciu (c)                 #
 # Date: 3/10/2019                                          #
 # Date: 3/10/2019                                          #
 # MIT Licence                                              #
 # MIT Licence                                              #
-# ########################################################## ##
+# ##########################################################
 
 
 from FlatCAMTool import FlatCAMTool
 from FlatCAMTool import FlatCAMTool
 from copy import copy, deepcopy
 from copy import copy, deepcopy
 from ObjectCollection import *
 from ObjectCollection import *
+from shapely.geometry import base
 
 
 import gettext
 import gettext
 import FlatCAMTranslation as fcTranslate
 import FlatCAMTranslation as fcTranslate
@@ -21,7 +22,7 @@ if '_' not in builtins.__dict__:
 
 
 class ToolPaint(FlatCAMTool, Gerber):
 class ToolPaint(FlatCAMTool, Gerber):
 
 
-    toolName = _("Paint Area")
+    toolName = _("Paint Tool")
 
 
     def __init__(self, app):
     def __init__(self, app):
         self.app = app
         self.app = app
@@ -51,18 +52,43 @@ class ToolPaint(FlatCAMTool, Gerber):
         form_layout = QtWidgets.QFormLayout()
         form_layout = QtWidgets.QFormLayout()
         self.tools_box.addLayout(form_layout)
         self.tools_box.addLayout(form_layout)
 
 
-        # ## Object
-        self.object_combo = QtWidgets.QComboBox()
-        self.object_combo.setModel(self.app.collection)
-        self.object_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
-        self.object_combo.setCurrentIndex(1)
-
-        self.object_label = QtWidgets.QLabel(_("Geometry:"))
-        self.object_label.setToolTip(
-            _("Geometry object to be painted.                        ")
+        # ################################################
+        # ##### Type of object to be painted #############
+        # ################################################
+        self.type_obj_combo = QtWidgets.QComboBox()
+        self.type_obj_combo.addItem("Gerber")
+        self.type_obj_combo.addItem("Excellon")
+        self.type_obj_combo.addItem("Geometry")
+
+        # we get rid of item1 ("Excellon") as it is not suitable
+        self.type_obj_combo.view().setRowHidden(1, True)
+        self.type_obj_combo.setItemIcon(0, QtGui.QIcon("share/flatcam_icon16.png"))
+        self.type_obj_combo.setItemIcon(2, QtGui.QIcon("share/geometry16.png"))
+
+        self.type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Obj Type"))
+        self.type_obj_combo_label.setToolTip(
+            _("Specify the type of object to be painted.\n"
+              "It can be of type: Gerber or Geometry.\n"
+              "What is selected here will dictate the kind\n"
+              "of objects that will populate the 'Object' combobox.")
         )
         )
+        self.type_obj_combo_label.setMinimumWidth(60)
+        form_layout.addRow(self.type_obj_combo_label, self.type_obj_combo)
+
+        # ################################################
+        # ##### The object to be painted #################
+        # ################################################
+        self.obj_combo = QtWidgets.QComboBox()
+        self.obj_combo.setModel(self.app.collection)
+        self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.obj_combo.setCurrentIndex(1)
+
+        self.object_label = QtWidgets.QLabel('%s:' % _("Object"))
+        self.object_label.setToolTip(_("Object to be painted."))
+
+        form_layout.addRow(self.object_label, self.obj_combo)
+
         e_lab_0 = QtWidgets.QLabel('')
         e_lab_0 = QtWidgets.QLabel('')
-        form_layout.addRow(self.object_label, self.object_combo)
         form_layout.addRow(e_lab_0)
         form_layout.addRow(e_lab_0)
 
 
         # ### Tools ## ##
         # ### Tools ## ##
@@ -107,8 +133,27 @@ class ToolPaint(FlatCAMTool, Gerber):
               "Choosing the <B>V-Shape</B> Tool Type automatically will select the Operation Type "
               "Choosing the <B>V-Shape</B> Tool Type automatically will select the Operation Type "
               "in the resulting geometry as Isolation."))
               "in the resulting geometry as Isolation."))
 
 
-        self.empty_label = QtWidgets.QLabel('')
-        self.tools_box.addWidget(self.empty_label)
+        self.order_label = QtWidgets.QLabel('<b>%s:</b>' % _('Tool order'))
+        self.order_label.setToolTip(_("This set the way that the tools in the tools table are used.\n"
+                                      "'No' --> means that the used order is the one in the tool table\n"
+                                      "'Forward' --> means that the tools will be ordered from small to big\n"
+                                      "'Reverse' --> menas that the tools will ordered from big to small\n\n"
+                                      "WARNING: using rest machining will automatically set the order\n"
+                                      "in reverse and disable this control."))
+
+        self.order_radio = RadioSet([{'label': _('No'), 'value': 'no'},
+                                     {'label': _('Forward'), 'value': 'fwd'},
+                                     {'label': _('Reverse'), 'value': 'rev'}])
+        self.order_radio.setToolTip(_("This set the way that the tools in the tools table are used.\n"
+                                      "'No' --> means that the used order is the one in the tool table\n"
+                                      "'Forward' --> means that the tools will be ordered from small to big\n"
+                                      "'Reverse' --> menas that the tools will ordered from big to small\n\n"
+                                      "WARNING: using rest machining will automatically set the order\n"
+                                      "in reverse and disable this control."))
+        form = QtWidgets.QFormLayout()
+        self.tools_box.addLayout(form)
+        form.addRow(QtWidgets.QLabel(''), QtWidgets.QLabel(''))
+        form.addRow(self.order_label, self.order_radio)
 
 
         # ### Add a new Tool ## ##
         # ### Add a new Tool ## ##
         hlay = QtWidgets.QHBoxLayout()
         hlay = QtWidgets.QHBoxLayout()
@@ -157,7 +202,7 @@ class ToolPaint(FlatCAMTool, Gerber):
         self.tools_box.addLayout(grid3)
         self.tools_box.addLayout(grid3)
 
 
         # Overlap
         # Overlap
-        ovlabel = QtWidgets.QLabel(_('Overlap Rate:'))
+        ovlabel = QtWidgets.QLabel('%s:' % _('Overlap Rate'))
         ovlabel.setToolTip(
         ovlabel.setToolTip(
             _("How much (fraction) of the tool width to overlap each tool pass.\n"
             _("How much (fraction) of the tool width to overlap each tool pass.\n"
               "Example:\n"
               "Example:\n"
@@ -174,7 +219,7 @@ class ToolPaint(FlatCAMTool, Gerber):
         grid3.addWidget(self.paintoverlap_entry, 1, 1)
         grid3.addWidget(self.paintoverlap_entry, 1, 1)
 
 
         # Margin
         # Margin
-        marginlabel = QtWidgets.QLabel(_('Margin:'))
+        marginlabel = QtWidgets.QLabel('%s:' % _('Margin'))
         marginlabel.setToolTip(
         marginlabel.setToolTip(
             _("Distance by which to avoid\n"
             _("Distance by which to avoid\n"
               "the edges of the polygon to\n"
               "the edges of the polygon to\n"
@@ -185,7 +230,7 @@ class ToolPaint(FlatCAMTool, Gerber):
         grid3.addWidget(self.paintmargin_entry, 2, 1)
         grid3.addWidget(self.paintmargin_entry, 2, 1)
 
 
         # Method
         # Method
-        methodlabel = QtWidgets.QLabel(_('Method:'))
+        methodlabel = QtWidgets.QLabel('%s:' % _('Method'))
         methodlabel.setToolTip(
         methodlabel.setToolTip(
             _("Algorithm for non-copper clearing:<BR>"
             _("Algorithm for non-copper clearing:<BR>"
               "<B>Standard</B>: Fixed step inwards.<BR>"
               "<B>Standard</B>: Fixed step inwards.<BR>"
@@ -201,7 +246,7 @@ class ToolPaint(FlatCAMTool, Gerber):
         grid3.addWidget(self.paintmethod_combo, 3, 1)
         grid3.addWidget(self.paintmethod_combo, 3, 1)
 
 
         # Connect lines
         # Connect lines
-        pathconnectlabel = QtWidgets.QLabel(_("Connect:"))
+        pathconnectlabel = QtWidgets.QLabel('%s:' % _("Connect"))
         pathconnectlabel.setToolTip(
         pathconnectlabel.setToolTip(
             _("Draw lines between resulting\n"
             _("Draw lines between resulting\n"
               "segments to minimize tool lifts.")
               "segments to minimize tool lifts.")
@@ -210,7 +255,7 @@ class ToolPaint(FlatCAMTool, Gerber):
         self.pathconnect_cb = FCCheckBox()
         self.pathconnect_cb = FCCheckBox()
         grid3.addWidget(self.pathconnect_cb, 4, 1)
         grid3.addWidget(self.pathconnect_cb, 4, 1)
 
 
-        contourlabel = QtWidgets.QLabel(_("Contour:"))
+        contourlabel = QtWidgets.QLabel('%s:' % _("Contour"))
         contourlabel.setToolTip(
         contourlabel.setToolTip(
             _("Cut around the perimeter of the polygon\n"
             _("Cut around the perimeter of the polygon\n"
               "to trim rough edges.")
               "to trim rough edges.")
@@ -219,7 +264,7 @@ class ToolPaint(FlatCAMTool, Gerber):
         self.paintcontour_cb = FCCheckBox()
         self.paintcontour_cb = FCCheckBox()
         grid3.addWidget(self.paintcontour_cb, 5, 1)
         grid3.addWidget(self.paintcontour_cb, 5, 1)
 
 
-        restlabel = QtWidgets.QLabel(_("Rest M.:"))
+        restlabel = QtWidgets.QLabel('%s:' % _("Rest M."))
         restlabel.setToolTip(
         restlabel.setToolTip(
             _("If checked, use 'rest machining'.\n"
             _("If checked, use 'rest machining'.\n"
               "Basically it will clear copper outside PCB features,\n"
               "Basically it will clear copper outside PCB features,\n"
@@ -234,30 +279,70 @@ class ToolPaint(FlatCAMTool, Gerber):
         grid3.addWidget(self.rest_cb, 6, 1)
         grid3.addWidget(self.rest_cb, 6, 1)
 
 
         # Polygon selection
         # Polygon selection
-        selectlabel = QtWidgets.QLabel(_('Selection:'))
+        selectlabel = QtWidgets.QLabel('%s:' % _('Selection'))
         selectlabel.setToolTip(
         selectlabel.setToolTip(
             _("How to select the polygons to paint.<BR>"
             _("How to select the polygons to paint.<BR>"
               "Options:<BR>"
               "Options:<BR>"
-              "- <B>Single</B>: left mouse click on the polygon to be painted.<BR>"
-              "- <B>All</B>: paint all polygons.")
+              "- <B>Single Polygons</B>: left mouse click on the polygon to be painted.<BR>"
+              "- <B>Area Selection</B>: left mouse click to start selection of the area to be painted.<BR>"
+              "- <B>All Polygons</B>: paint all polygons.<BR>"
+              "- <B>Reference Object</B>: paint an area described by an external reference object.")
         )
         )
         grid3.addWidget(selectlabel, 7, 0)
         grid3.addWidget(selectlabel, 7, 0)
         # grid3 = QtWidgets.QGridLayout()
         # grid3 = QtWidgets.QGridLayout()
         self.selectmethod_combo = RadioSet([
         self.selectmethod_combo = RadioSet([
-            {"label": _("Single"), "value": "single"},
-            {"label": _("Area"), "value": "area"},
-            {"label": _("All"), "value": "all"}
-        ])
+            {"label": _("Single Polygon"), "value": "single"},
+            {"label": _("Area Selection"), "value": "area"},
+            {"label": _("All Polygons"), "value": "all"},
+            {"label": _("Reference Object"), "value": "ref"}
+        ], orientation='vertical', stretch=False)
+        self.selectmethod_combo.setToolTip(
+            _("How to select Polygons to be painted.\n\n"
+              "- 'Area Selection' - left mouse click to start selection of the area to be painted.\n"
+              "Keeping a modifier key pressed (CTRL or SHIFT) will allow to add multiple areas.\n"
+              "- 'All Polygons' - the Paint will start after click.\n"
+              "- 'Reference Object' -  will do non copper clearing within the area\n"
+              "specified by another object.")
+        )
         grid3.addWidget(self.selectmethod_combo, 7, 1)
         grid3.addWidget(self.selectmethod_combo, 7, 1)
 
 
+        form1 = QtWidgets.QFormLayout()
+        self.tools_box.addLayout(form1)
+
+        self.box_combo_type_label = QtWidgets.QLabel('%s:' % _("Ref. Type"))
+        self.box_combo_type_label.setToolTip(
+            _("The type of FlatCAM object to be used as paint reference.\n"
+              "It can be Gerber, Excellon or Geometry.")
+        )
+        self.box_combo_type = QtWidgets.QComboBox()
+        self.box_combo_type.addItem(_("Gerber   Reference Box Object"))
+        self.box_combo_type.addItem(_("Excellon Reference Box Object"))
+        self.box_combo_type.addItem(_("Geometry Reference Box Object"))
+        form1.addRow(self.box_combo_type_label, self.box_combo_type)
+
+        self.box_combo_label = QtWidgets.QLabel('%s:' % _("Ref. Object"))
+        self.box_combo_label.setToolTip(
+            _("The FlatCAM object to be used as non copper clearing reference.")
+        )
+        self.box_combo = QtWidgets.QComboBox()
+        self.box_combo.setModel(self.app.collection)
+        self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.box_combo.setCurrentIndex(1)
+        form1.addRow(self.box_combo_label, self.box_combo)
+
+        self.box_combo.hide()
+        self.box_combo_label.hide()
+        self.box_combo_type.hide()
+        self.box_combo_type_label.hide()
+
         # GO Button
         # GO Button
         self.generate_paint_button = QtWidgets.QPushButton(_('Create Paint Geometry'))
         self.generate_paint_button = QtWidgets.QPushButton(_('Create Paint Geometry'))
         self.generate_paint_button.setToolTip(
         self.generate_paint_button.setToolTip(
-            _("After clicking here, click inside<BR>"
-              "the polygon you wish to be painted if <B>Single</B> is selected.<BR>"
-              "If <B>All</B>  is selected then the Paint will start after click.<BR>"
-              "A new Geometry object with the tool<BR>"
-              "paths will be created.")
+            _("- 'Area Selection' - left mouse click to start selection of the area to be painted.\n"
+              "Keeping a modifier key pressed (CTRL or SHIFT) will allow to add multiple areas.\n"
+              "- 'All Polygons' - the Paint will start after click.\n"
+              "- 'Reference Object' -  will do non copper clearing within the area\n"
+              "specified by another object.")
         )
         )
         self.tools_box.addWidget(self.generate_paint_button)
         self.tools_box.addWidget(self.generate_paint_button)
 
 
@@ -271,6 +356,9 @@ class ToolPaint(FlatCAMTool, Gerber):
         self.tooluid = 0
         self.tooluid = 0
         self.first_click = False
         self.first_click = False
         self.cursor_pos = None
         self.cursor_pos = None
+        self.mouse_is_dragging = False
+
+        self.sel_rect = []
 
 
         # store here the default data for Geometry Data
         # store here the default data for Geometry Data
         self.default_data = {}
         self.default_data = {}
@@ -316,6 +404,16 @@ class ToolPaint(FlatCAMTool, Gerber):
         self.deltool_btn.clicked.connect(self.on_tool_delete)
         self.deltool_btn.clicked.connect(self.on_tool_delete)
         self.generate_paint_button.clicked.connect(self.on_paint_button_click)
         self.generate_paint_button.clicked.connect(self.on_paint_button_click)
         self.selectmethod_combo.activated_custom.connect(self.on_radio_selection)
         self.selectmethod_combo.activated_custom.connect(self.on_radio_selection)
+        self.order_radio.activated_custom[str].connect(self.on_order_changed)
+        self.rest_cb.stateChanged.connect(self.on_rest_machining_check)
+
+        self.box_combo_type.currentIndexChanged.connect(self.on_combo_box_type)
+        self.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
+
+    def on_type_obj_index_changed(self, index):
+        obj_type = self.type_obj_combo.currentIndex()
+        self.obj_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
+        self.obj_combo.setCurrentIndex(0)
 
 
     def install(self, icon=None, separator=None, **kwargs):
     def install(self, icon=None, separator=None, **kwargs):
         FlatCAMTool.install(self, icon, separator, shortcut='ALT+P', **kwargs)
         FlatCAMTool.install(self, icon, separator, shortcut='ALT+P', **kwargs)
@@ -330,7 +428,12 @@ class ToolPaint(FlatCAMTool, Gerber):
             else:
             else:
                 try:
                 try:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
-                        self.app.ui.splitter.setSizes([0, 1])
+                        # if tab is populated with the tool but it does not have the focus, focus on it
+                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
+                            # focus on Tool Tab
+                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
+                        else:
+                            self.app.ui.splitter.setSizes([0, 1])
                 except AttributeError:
                 except AttributeError:
                     pass
                     pass
         else:
         else:
@@ -342,7 +445,29 @@ class ToolPaint(FlatCAMTool, Gerber):
 
 
         self.app.ui.notebook.setTabText(2, _("Paint Tool"))
         self.app.ui.notebook.setTabText(2, _("Paint Tool"))
 
 
+    def reset_usage(self):
+        self.obj_name = ""
+        self.paint_obj = None
+        self.bound_obj = None
+
+        self.first_click = False
+        self.cursor_pos = None
+        self.mouse_is_dragging = False
+
+        self.sel_rect = []
+
     def on_radio_selection(self):
     def on_radio_selection(self):
+        if self.selectmethod_combo.get_value() == "ref":
+            self.box_combo.show()
+            self.box_combo_label.show()
+            self.box_combo_type.show()
+            self.box_combo_type_label.show()
+        else:
+            self.box_combo.hide()
+            self.box_combo_label.hide()
+            self.box_combo_type.hide()
+            self.box_combo_type_label.hide()
+
         if self.selectmethod_combo.get_value() == 'single':
         if self.selectmethod_combo.get_value() == 'single':
             # disable rest-machining for single polygon painting
             # disable rest-machining for single polygon painting
             self.rest_cb.set_value(False)
             self.rest_cb.set_value(False)
@@ -367,11 +492,25 @@ class ToolPaint(FlatCAMTool, Gerber):
             self.deltool_btn.setDisabled(False)
             self.deltool_btn.setDisabled(False)
             self.tools_table.setContextMenuPolicy(Qt.ActionsContextMenu)
             self.tools_table.setContextMenuPolicy(Qt.ActionsContextMenu)
 
 
+    def on_order_changed(self, order):
+        if order != 'no':
+            self.build_ui()
+
+    def on_rest_machining_check(self, state):
+        if state:
+            self.order_radio.set_value('rev')
+            self.order_label.setDisabled(True)
+            self.order_radio.setDisabled(True)
+        else:
+            self.order_label.setDisabled(False)
+            self.order_radio.setDisabled(False)
+
     def set_tool_ui(self):
     def set_tool_ui(self):
         self.tools_frame.show()
         self.tools_frame.show()
         self.reset_fields()
         self.reset_fields()
 
 
         # ## Init the GUI interface
         # ## Init the GUI interface
+        self.order_radio.set_value(self.app.defaults["tools_paintorder"])
         self.paintmargin_entry.set_value(self.default_data["paintmargin"])
         self.paintmargin_entry.set_value(self.default_data["paintmargin"])
         self.paintmethod_combo.set_value(self.default_data["paintmethod"])
         self.paintmethod_combo.set_value(self.default_data["paintmethod"])
         self.selectmethod_combo.set_value(self.default_data["selectmethod"])
         self.selectmethod_combo.set_value(self.default_data["selectmethod"])
@@ -452,7 +591,14 @@ class ToolPaint(FlatCAMTool, Gerber):
         sorted_tools = []
         sorted_tools = []
         for k, v in self.paint_tools.items():
         for k, v in self.paint_tools.items():
             sorted_tools.append(float('%.4f' % float(v['tooldia'])))
             sorted_tools.append(float('%.4f' % float(v['tooldia'])))
-        sorted_tools.sort()
+
+        order = self.order_radio.get_value()
+        if order == 'fwd':
+            sorted_tools.sort(reverse=False)
+        elif order == 'rev':
+            sorted_tools.sort(reverse=True)
+        else:
+            pass
 
 
         n = len(sorted_tools)
         n = len(sorted_tools)
         self.tools_table.setRowCount(n)
         self.tools_table.setRowCount(n)
@@ -523,6 +669,11 @@ class ToolPaint(FlatCAMTool, Gerber):
         # we reactivate the signals after the after the tool adding as we don't need to see the tool been populated
         # we reactivate the signals after the after the tool adding as we don't need to see the tool been populated
         self.tools_table.itemChanged.connect(self.on_tool_edit)
         self.tools_table.itemChanged.connect(self.on_tool_edit)
 
 
+    def on_combo_box_type(self):
+        obj_type = self.box_combo_type.currentIndex()
+        self.box_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
+        self.box_combo.setCurrentIndex(0)
+
     def on_tool_add(self, dia=None, muted=None):
     def on_tool_add(self, dia=None, muted=None):
 
 
         try:
         try:
@@ -541,7 +692,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                     tool_dia = float(self.addtool_entry.get_value().replace(',', '.'))
                     tool_dia = float(self.addtool_entry.get_value().replace(',', '.'))
                 except ValueError:
                 except ValueError:
                     self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
                     self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
-                                         "use a number."))
+                                           "use a number."))
                     return
                     return
 
 
             if tool_dia is None:
             if tool_dia is None:
@@ -739,6 +890,10 @@ class ToolPaint(FlatCAMTool, Gerber):
         self.build_ui()
         self.build_ui()
 
 
     def on_paint_button_click(self):
     def on_paint_button_click(self):
+
+        # init values for the next usage
+        self.reset_usage()
+
         self.app.report_usage(_("geometry_on_paint_button"))
         self.app.report_usage(_("geometry_on_paint_button"))
         # self.app.call_source = 'paint'
         # self.app.call_source = 'paint'
 
 
@@ -764,7 +919,7 @@ class ToolPaint(FlatCAMTool, Gerber):
         contour = self.paintcontour_cb.get_value()
         contour = self.paintcontour_cb.get_value()
         select_method = self.selectmethod_combo.get_value()
         select_method = self.selectmethod_combo.get_value()
 
 
-        self.obj_name = self.object_combo.currentText()
+        self.obj_name = self.obj_combo.currentText()
 
 
         # Get source object.
         # Get source object.
         try:
         try:
@@ -828,7 +983,7 @@ class ToolPaint(FlatCAMTool, Gerber):
             tooldia = float('%.4f' % float(self.tools_table.item(0, 1).text()))
             tooldia = float('%.4f' % float(self.tools_table.item(0, 1).text()))
 
 
             # To be called after clicking on the plot.
             # To be called after clicking on the plot.
-            def on_mouse_press(event):
+            def on_mouse_release(event):
                 # do paint single only for left mouse clicks
                 # do paint single only for left mouse clicks
                 if event.button == 1:
                 if event.button == 1:
                     if not self.first_click:
                     if not self.first_click:
@@ -839,8 +994,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                         if self.app.grid_status() == True:
                         if self.app.grid_status() == True:
                             self.cursor_pos = self.app.geo_editor.snap(self.cursor_pos[0], self.cursor_pos[1])
                             self.cursor_pos = self.app.geo_editor.snap(self.cursor_pos[0], self.cursor_pos[1])
                     else:
                     else:
-                        self.app.inform.emit(_("Done."))
-                        self.first_click = False
+                        self.app.inform.emit(_("Zone added. Right click to finish."))
                         self.app.delete_selection_shape()
                         self.app.delete_selection_shape()
 
 
                         curr_pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
                         curr_pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
@@ -853,27 +1007,63 @@ class ToolPaint(FlatCAMTool, Gerber):
                         pt2 = (x1, y0)
                         pt2 = (x1, y0)
                         pt3 = (x1, y1)
                         pt3 = (x1, y1)
                         pt4 = (x0, y1)
                         pt4 = (x0, y1)
-                        sel_rect = Polygon([pt1, pt2, pt3, pt4])
+                        self.sel_rect.append(Polygon([pt1, pt2, pt3, pt4]))
+
+                        modifiers = QtWidgets.QApplication.keyboardModifiers()
+
+                        if modifiers == QtCore.Qt.ShiftModifier:
+                            mod_key = 'Shift'
+                        elif modifiers == QtCore.Qt.ControlModifier:
+                            mod_key = 'Control'
+                        else:
+                            mod_key = None
 
 
+                        if mod_key == self.app.defaults["global_mselect_key"]:
+                            self.first_click = False
+                            return
+
+                        self.sel_rect = cascaded_union(self.sel_rect)
                         self.paint_poly_area(obj=self.paint_obj,
                         self.paint_poly_area(obj=self.paint_obj,
-                                             sel_obj= sel_rect,
+                                             sel_obj= self.sel_rect,
                                              outname=o_name,
                                              outname=o_name,
                                              overlap=overlap,
                                              overlap=overlap,
                                              connect=connect,
                                              connect=connect,
                                              contour=contour)
                                              contour=contour)
 
 
-                        self.app.plotcanvas.vis_disconnect('mouse_press', on_mouse_press)
+                        self.app.plotcanvas.vis_disconnect('mouse_release', on_mouse_release)
                         self.app.plotcanvas.vis_disconnect('mouse_move', on_mouse_move)
                         self.app.plotcanvas.vis_disconnect('mouse_move', on_mouse_move)
 
 
                         self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
                         self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
                         self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
                         self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
                         self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
                         self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
+                elif event.button == 2 and self.first_click is False and self.mouse_is_dragging is False:
+                    self.first_click = False
+                    self.app.plotcanvas.vis_disconnect('mouse_release', on_mouse_release)
+                    self.app.plotcanvas.vis_disconnect('mouse_move', on_mouse_move)
+
+                    self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
+                    self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
+                    self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
+
+                    self.sel_rect = cascaded_union(self.sel_rect)
+                    self.paint_poly_area(obj=self.paint_obj,
+                                         sel_obj=self.sel_rect,
+                                         outname=o_name,
+                                         overlap=overlap,
+                                         connect=connect,
+                                         contour=contour)
 
 
             # called on mouse move
             # called on mouse move
             def on_mouse_move(event):
             def on_mouse_move(event):
                 curr_pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
                 curr_pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
                 self.app.app_cursor.enabled = False
                 self.app.app_cursor.enabled = False
 
 
+                if event.button == 2:
+                    if event.is_dragging is True:
+                        self.mouse_is_dragging = True
+                    else:
+                        self.mouse_is_dragging = False
+
                 if self.app.grid_status() == True:
                 if self.app.grid_status() == True:
                     self.app.app_cursor.enabled = True
                     self.app.app_cursor.enabled = True
                     # Update cursor
                     # Update cursor
@@ -891,9 +1081,41 @@ class ToolPaint(FlatCAMTool, Gerber):
             self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
             self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
             self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
             self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
 
 
-            self.app.plotcanvas.vis_connect('mouse_press', on_mouse_press)
+            self.app.plotcanvas.vis_connect('mouse_release', on_mouse_release)
             self.app.plotcanvas.vis_connect('mouse_move', on_mouse_move)
             self.app.plotcanvas.vis_connect('mouse_move', on_mouse_move)
 
 
+        elif select_method == 'ref':
+            self.bound_obj_name = self.box_combo.currentText()
+            # Get source object.
+            try:
+                self.bound_obj = self.app.collection.get_by_name(self.bound_obj_name)
+            except Exception as e:
+                self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % self.obj_name)
+                return "Could not retrieve object: %s" % self.obj_name
+
+            geo = self.bound_obj.solid_geometry
+            try:
+                if isinstance(geo, MultiPolygon):
+                    env_obj = geo.convex_hull
+                elif (isinstance(geo, MultiPolygon) and len(geo) == 1) or \
+                    (isinstance(geo, list) and len(geo) == 1) and isinstance(geo[0], Polygon):
+                    env_obj = cascaded_union(self.bound_obj.solid_geometry)
+                else:
+                    env_obj = cascaded_union(self.bound_obj.solid_geometry)
+                    env_obj = env_obj.convex_hull
+                sel_rect = env_obj.buffer(distance=0.0000001, join_style=base.JOIN_STYLE.mitre)
+            except Exception as e:
+                log.debug("ToolPaint.on_paint_button_click() --> %s" % str(e))
+                self.app.inform.emit(_("[ERROR_NOTCL] No object available."))
+                return
+
+            self.paint_poly_area(obj=self.paint_obj,
+                                 sel_obj=sel_rect,
+                                 outname=o_name,
+                                 overlap=overlap,
+                                 connect=connect,
+                                 contour=contour)
+
     def paint_poly(self, obj, inside_pt, tooldia, overlap, outname=None, connect=True, contour=True):
     def paint_poly(self, obj, inside_pt, tooldia, overlap, outname=None, connect=True, contour=True):
         """
         """
         Paints a polygon selected by clicking on its interior.
         Paints a polygon selected by clicking on its interior.
@@ -1140,7 +1362,14 @@ class ToolPaint(FlatCAMTool, Gerber):
             sorted_tools = []
             sorted_tools = []
             for row in range(self.tools_table.rowCount()):
             for row in range(self.tools_table.rowCount()):
                 sorted_tools.append(float(self.tools_table.item(row, 1).text()))
                 sorted_tools.append(float(self.tools_table.item(row, 1).text()))
-            sorted_tools.sort(reverse=True)
+
+            order = self.order_radio.get_value()
+            if order == 'fwd':
+                sorted_tools.sort(reverse=False)
+            elif order == 'rev':
+                sorted_tools.sort(reverse=True)
+            else:
+                pass
 
 
             try:
             try:
                 a, b, c, d = obj.bounds()
                 a, b, c, d = obj.bounds()
@@ -1426,10 +1655,22 @@ class ToolPaint(FlatCAMTool, Gerber):
             sorted_tools = []
             sorted_tools = []
             for row in range(self.tools_table.rowCount()):
             for row in range(self.tools_table.rowCount()):
                 sorted_tools.append(float(self.tools_table.item(row, 1).text()))
                 sorted_tools.append(float(self.tools_table.item(row, 1).text()))
-            sorted_tools.sort(reverse=True)
+
+            order = self.order_radio.get_value()
+            if order == 'fwd':
+                sorted_tools.sort(reverse=False)
+            elif order == 'rev':
+                sorted_tools.sort(reverse=True)
+            else:
+                pass
 
 
             geo_to_paint = []
             geo_to_paint = []
-            for poly in obj.solid_geometry:
+            if not isinstance(obj.solid_geometry, list):
+                target_geo = [obj.solid_geometry]
+            else:
+                target_geo = obj.solid_geometry
+
+            for poly in target_geo:
                 new_pol = poly.intersection(sel_obj)
                 new_pol = poly.intersection(sel_obj)
                 geo_to_paint.append(new_pol)
                 geo_to_paint.append(new_pol)
 
 
@@ -1674,4 +1915,4 @@ class ToolPaint(FlatCAMTool, Gerber):
         return bounds_rec(geometry)
         return bounds_rec(geometry)
 
 
     def reset_fields(self):
     def reset_fields(self):
-        self.object_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
+        self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))

+ 20 - 15
flatcamTools/ToolPanelize.py

@@ -53,7 +53,7 @@ class Panelize(FlatCAMTool):
         self.type_obj_combo.setItemIcon(1, QtGui.QIcon("share/drill16.png"))
         self.type_obj_combo.setItemIcon(1, QtGui.QIcon("share/drill16.png"))
         self.type_obj_combo.setItemIcon(2, QtGui.QIcon("share/geometry16.png"))
         self.type_obj_combo.setItemIcon(2, QtGui.QIcon("share/geometry16.png"))
 
 
-        self.type_obj_combo_label = QtWidgets.QLabel(_("Object Type:"))
+        self.type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Object Type"))
         self.type_obj_combo_label.setToolTip(
         self.type_obj_combo_label.setToolTip(
             _("Specify the type of object to be panelized\n"
             _("Specify the type of object to be panelized\n"
               "It can be of type: Gerber, Excellon or Geometry.\n"
               "It can be of type: Gerber, Excellon or Geometry.\n"
@@ -68,7 +68,7 @@ class Panelize(FlatCAMTool):
         self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.object_combo.setCurrentIndex(1)
         self.object_combo.setCurrentIndex(1)
 
 
-        self.object_label = QtWidgets.QLabel(_("Object:"))
+        self.object_label = QtWidgets.QLabel('%s:' % _("Object"))
         self.object_label.setToolTip(
         self.object_label.setToolTip(
             _("Object to be panelized. This means that it will\n"
             _("Object to be panelized. This means that it will\n"
               "be duplicated in an array of rows and columns.")
               "be duplicated in an array of rows and columns.")
@@ -83,7 +83,7 @@ class Panelize(FlatCAMTool):
         # Type of box Panel object
         # Type of box Panel object
         self.reference_radio = RadioSet([{'label': _('Object'), 'value': 'object'},
         self.reference_radio = RadioSet([{'label': _('Object'), 'value': 'object'},
                                          {'label': _('Bounding Box'), 'value': 'bbox'}])
                                          {'label': _('Bounding Box'), 'value': 'bbox'}])
-        self.box_label = QtWidgets.QLabel(_("<b>Penelization Reference:</b>"))
+        self.box_label = QtWidgets.QLabel("<b>%s:</b>" % _("Penelization Reference"))
         self.box_label.setToolTip(
         self.box_label.setToolTip(
             _("Choose the reference for panelization:\n"
             _("Choose the reference for panelization:\n"
               "- Object = the bounding box of a different object\n"
               "- Object = the bounding box of a different object\n"
@@ -108,7 +108,7 @@ class Panelize(FlatCAMTool):
         self.type_box_combo.setItemIcon(0, QtGui.QIcon("share/flatcam_icon16.png"))
         self.type_box_combo.setItemIcon(0, QtGui.QIcon("share/flatcam_icon16.png"))
         self.type_box_combo.setItemIcon(2, QtGui.QIcon("share/geometry16.png"))
         self.type_box_combo.setItemIcon(2, QtGui.QIcon("share/geometry16.png"))
 
 
-        self.type_box_combo_label = QtWidgets.QLabel(_("Box Type:"))
+        self.type_box_combo_label = QtWidgets.QLabel('%s:' % _("Box Type"))
         self.type_box_combo_label.setToolTip(
         self.type_box_combo_label.setToolTip(
             _("Specify the type of object to be used as an container for\n"
             _("Specify the type of object to be used as an container for\n"
               "panelization. It can be: Gerber or Geometry type.\n"
               "panelization. It can be: Gerber or Geometry type.\n"
@@ -123,7 +123,7 @@ class Panelize(FlatCAMTool):
         self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.box_combo.setCurrentIndex(1)
         self.box_combo.setCurrentIndex(1)
 
 
-        self.box_combo_label = QtWidgets.QLabel(_("Box Object:"))
+        self.box_combo_label = QtWidgets.QLabel('%s:' % _("Box Object"))
         self.box_combo_label.setToolTip(
         self.box_combo_label.setToolTip(
             _("The actual object that is used a container for the\n "
             _("The actual object that is used a container for the\n "
               "selected object that is to be panelized.")
               "selected object that is to be panelized.")
@@ -131,7 +131,7 @@ class Panelize(FlatCAMTool):
         form_layout.addRow(self.box_combo_label, self.box_combo)
         form_layout.addRow(self.box_combo_label, self.box_combo)
         form_layout.addRow(QtWidgets.QLabel(""))
         form_layout.addRow(QtWidgets.QLabel(""))
 
 
-        panel_data_label = QtWidgets.QLabel(_("<b>Panel Data:</b>"))
+        panel_data_label = QtWidgets.QLabel("<b>%s:</b>" % _("Panel Data"))
         panel_data_label.setToolTip(
         panel_data_label.setToolTip(
             _("This informations will shape the resulting panel.\n"
             _("This informations will shape the resulting panel.\n"
               "The number of rows and columns will set how many\n"
               "The number of rows and columns will set how many\n"
@@ -144,7 +144,7 @@ class Panelize(FlatCAMTool):
 
 
         # Spacing Columns
         # Spacing Columns
         self.spacing_columns = FCEntry()
         self.spacing_columns = FCEntry()
-        self.spacing_columns_label = QtWidgets.QLabel(_("Spacing cols:"))
+        self.spacing_columns_label = QtWidgets.QLabel('%s:' % _("Spacing cols"))
         self.spacing_columns_label.setToolTip(
         self.spacing_columns_label.setToolTip(
             _("Spacing between columns of the desired panel.\n"
             _("Spacing between columns of the desired panel.\n"
               "In current units.")
               "In current units.")
@@ -153,7 +153,7 @@ class Panelize(FlatCAMTool):
 
 
         # Spacing Rows
         # Spacing Rows
         self.spacing_rows = FCEntry()
         self.spacing_rows = FCEntry()
-        self.spacing_rows_label = QtWidgets.QLabel(_("Spacing rows:"))
+        self.spacing_rows_label = QtWidgets.QLabel('%s:' % _("Spacing rows"))
         self.spacing_rows_label.setToolTip(
         self.spacing_rows_label.setToolTip(
             _("Spacing between rows of the desired panel.\n"
             _("Spacing between rows of the desired panel.\n"
               "In current units.")
               "In current units.")
@@ -162,7 +162,7 @@ class Panelize(FlatCAMTool):
 
 
         # Columns
         # Columns
         self.columns = FCEntry()
         self.columns = FCEntry()
-        self.columns_label = QtWidgets.QLabel(_("Columns:"))
+        self.columns_label = QtWidgets.QLabel('%s:' % _("Columns"))
         self.columns_label.setToolTip(
         self.columns_label.setToolTip(
             _("Number of columns of the desired panel")
             _("Number of columns of the desired panel")
         )
         )
@@ -170,7 +170,7 @@ class Panelize(FlatCAMTool):
 
 
         # Rows
         # Rows
         self.rows = FCEntry()
         self.rows = FCEntry()
-        self.rows_label = QtWidgets.QLabel(_("Rows:"))
+        self.rows_label = QtWidgets.QLabel('%s:' % _("Rows"))
         self.rows_label.setToolTip(
         self.rows_label.setToolTip(
             _("Number of rows of the desired panel")
             _("Number of rows of the desired panel")
         )
         )
@@ -180,7 +180,7 @@ class Panelize(FlatCAMTool):
         # Type of resulting Panel object
         # Type of resulting Panel object
         self.panel_type_radio = RadioSet([{'label': _('Gerber'), 'value': 'gerber'},
         self.panel_type_radio = RadioSet([{'label': _('Gerber'), 'value': 'gerber'},
                                           {'label': _('Geo'), 'value': 'geometry'}])
                                           {'label': _('Geo'), 'value': 'geometry'}])
-        self.panel_type_label = QtWidgets.QLabel(_("<b>Panel Type:</b>"))
+        self.panel_type_label = QtWidgets.QLabel("<b>%s:</b>" % _("Panel Type"))
         self.panel_type_label.setToolTip(
         self.panel_type_label.setToolTip(
             _("Choose the type of object for the panel object:\n"
             _("Choose the type of object for the panel object:\n"
               "- Geometry\n"
               "- Geometry\n"
@@ -190,7 +190,7 @@ class Panelize(FlatCAMTool):
         form_layout.addRow(self.panel_type_radio)
         form_layout.addRow(self.panel_type_radio)
 
 
         # Constrains
         # Constrains
-        self.constrain_cb = FCCheckBox(_("Constrain panel within:"))
+        self.constrain_cb = FCCheckBox('%s:' % _("Constrain panel within"))
         self.constrain_cb.setToolTip(
         self.constrain_cb.setToolTip(
             _("Area define by DX and DY within to constrain the panel.\n"
             _("Area define by DX and DY within to constrain the panel.\n"
               "DX and DY values are in current units.\n"
               "DX and DY values are in current units.\n"
@@ -201,7 +201,7 @@ class Panelize(FlatCAMTool):
         form_layout.addRow(self.constrain_cb)
         form_layout.addRow(self.constrain_cb)
 
 
         self.x_width_entry = FCEntry()
         self.x_width_entry = FCEntry()
-        self.x_width_lbl = QtWidgets.QLabel(_("Width (DX):"))
+        self.x_width_lbl = QtWidgets.QLabel('%s:' % _("Width (DX)"))
         self.x_width_lbl.setToolTip(
         self.x_width_lbl.setToolTip(
             _("The width (DX) within which the panel must fit.\n"
             _("The width (DX) within which the panel must fit.\n"
               "In current units.")
               "In current units.")
@@ -209,7 +209,7 @@ class Panelize(FlatCAMTool):
         form_layout.addRow(self.x_width_lbl, self.x_width_entry)
         form_layout.addRow(self.x_width_lbl, self.x_width_entry)
 
 
         self.y_height_entry = FCEntry()
         self.y_height_entry = FCEntry()
-        self.y_height_lbl = QtWidgets.QLabel(_("Height (DY):"))
+        self.y_height_lbl = QtWidgets.QLabel('%s:' % _("Height (DY)"))
         self.y_height_lbl.setToolTip(
         self.y_height_lbl.setToolTip(
             _("The height (DY)within which the panel must fit.\n"
             _("The height (DY)within which the panel must fit.\n"
               "In current units.")
               "In current units.")
@@ -259,7 +259,12 @@ class Panelize(FlatCAMTool):
             else:
             else:
                 try:
                 try:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
-                        self.app.ui.splitter.setSizes([0, 1])
+                        # if tab is populated with the tool but it does not have the focus, focus on it
+                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
+                            # focus on Tool Tab
+                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
+                        else:
+                            self.app.ui.splitter.setSizes([0, 1])
                 except AttributeError:
                 except AttributeError:
                     pass
                     pass
         else:
         else:

+ 13 - 8
flatcamTools/ToolPcbWizard.py

@@ -48,13 +48,13 @@ class PcbWizard(FlatCAMTool):
         self.layout.addWidget(title_label)
         self.layout.addWidget(title_label)
 
 
         self.layout.addWidget(QtWidgets.QLabel(""))
         self.layout.addWidget(QtWidgets.QLabel(""))
-        self.layout.addWidget(QtWidgets.QLabel(_("<b>Load files:</b>")))
+        self.layout.addWidget(QtWidgets.QLabel("<b>%s:</b>" % _("Load files")))
 
 
         # Form Layout
         # Form Layout
         form_layout = QtWidgets.QFormLayout()
         form_layout = QtWidgets.QFormLayout()
         self.layout.addLayout(form_layout)
         self.layout.addLayout(form_layout)
 
 
-        self.excellon_label = QtWidgets.QLabel(_("Excellon file:"))
+        self.excellon_label = QtWidgets.QLabel('%s:' % _("Excellon file"))
         self.excellon_label.setToolTip(
         self.excellon_label.setToolTip(
            _("Load the Excellon file.\n"
            _("Load the Excellon file.\n"
              "Usually it has a .DRL extension")
              "Usually it has a .DRL extension")
@@ -62,7 +62,7 @@ class PcbWizard(FlatCAMTool):
         self.excellon_brn = FCButton(_("Open"))
         self.excellon_brn = FCButton(_("Open"))
         form_layout.addRow(self.excellon_label, self.excellon_brn)
         form_layout.addRow(self.excellon_label, self.excellon_brn)
 
 
-        self.inf_label = QtWidgets.QLabel(_("INF file:"))
+        self.inf_label = QtWidgets.QLabel('%s:' % _("INF file"))
         self.inf_label.setToolTip(
         self.inf_label.setToolTip(
             _("Load the INF file.")
             _("Load the INF file.")
         )
         )
@@ -84,7 +84,7 @@ class PcbWizard(FlatCAMTool):
         self.tools_table.setVisible(False)
         self.tools_table.setVisible(False)
 
 
         self.layout.addWidget(QtWidgets.QLabel(""))
         self.layout.addWidget(QtWidgets.QLabel(""))
-        self.layout.addWidget(QtWidgets.QLabel(_("<b>Excellon format:</b>")))
+        self.layout.addWidget(QtWidgets.QLabel("<b>%s:</b>" % _("Excellon format")))
         # Form Layout
         # Form Layout
         form_layout1 = QtWidgets.QFormLayout()
         form_layout1 = QtWidgets.QFormLayout()
         self.layout.addLayout(form_layout1)
         self.layout.addLayout(form_layout1)
@@ -92,7 +92,7 @@ class PcbWizard(FlatCAMTool):
         # Integral part of the coordinates
         # Integral part of the coordinates
         self.int_entry = FCSpinner()
         self.int_entry = FCSpinner()
         self.int_entry.set_range(1, 10)
         self.int_entry.set_range(1, 10)
-        self.int_label = QtWidgets.QLabel(_("Int. digits:"))
+        self.int_label = QtWidgets.QLabel('%s:' % _("Int. digits"))
         self.int_label.setToolTip(
         self.int_label.setToolTip(
            _("The number of digits for the integral part of the coordinates.")
            _("The number of digits for the integral part of the coordinates.")
         )
         )
@@ -101,7 +101,7 @@ class PcbWizard(FlatCAMTool):
         # Fractional part of the coordinates
         # Fractional part of the coordinates
         self.frac_entry = FCSpinner()
         self.frac_entry = FCSpinner()
         self.frac_entry.set_range(1, 10)
         self.frac_entry.set_range(1, 10)
-        self.frac_label = QtWidgets.QLabel(_("Frac. digits:"))
+        self.frac_label = QtWidgets.QLabel('%s:' % _("Frac. digits"))
         self.frac_label.setToolTip(
         self.frac_label.setToolTip(
             _("The number of digits for the fractional part of the coordinates.")
             _("The number of digits for the fractional part of the coordinates.")
         )
         )
@@ -111,7 +111,7 @@ class PcbWizard(FlatCAMTool):
         self.zeros_radio = RadioSet([{'label': _('LZ'), 'value': 'LZ'},
         self.zeros_radio = RadioSet([{'label': _('LZ'), 'value': 'LZ'},
                                      {'label': _('TZ'), 'value': 'TZ'},
                                      {'label': _('TZ'), 'value': 'TZ'},
                                      {'label': _('No Suppression'), 'value': 'D'}])
                                      {'label': _('No Suppression'), 'value': 'D'}])
-        self.zeros_label = QtWidgets.QLabel(_("Zeros supp.:"))
+        self.zeros_label = QtWidgets.QLabel('%s:' % _("Zeros supp."))
         self.zeros_label.setToolTip(
         self.zeros_label.setToolTip(
             _("The type of zeros suppression used.\n"
             _("The type of zeros suppression used.\n"
               "Can be of type:\n"
               "Can be of type:\n"
@@ -179,7 +179,12 @@ class PcbWizard(FlatCAMTool):
             else:
             else:
                 try:
                 try:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
-                        self.app.ui.splitter.setSizes([0, 1])
+                        # if tab is populated with the tool but it does not have the focus, focus on it
+                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
+                            # focus on Tool Tab
+                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
+                        else:
+                            self.app.ui.splitter.setSizes([0, 1])
                 except AttributeError:
                 except AttributeError:
                     pass
                     pass
         else:
         else:

+ 48 - 20
flatcamTools/ToolProperties.py

@@ -77,7 +77,12 @@ class Properties(FlatCAMTool):
             else:
             else:
                 try:
                 try:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
-                        self.app.ui.splitter.setSizes([0, 1])
+                        # if tab is populated with the tool but it does not have the focus, focus on it
+                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
+                            # focus on Tool Tab
+                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
+                        else:
+                            self.app.ui.splitter.setSizes([0, 1])
                 except AttributeError:
                 except AttributeError:
                     pass
                     pass
         else:
         else:
@@ -117,23 +122,24 @@ class Properties(FlatCAMTool):
 
 
         font = QtGui.QFont()
         font = QtGui.QFont()
         font.setBold(True)
         font.setBold(True)
-        obj_type = self.addParent(parent, 'TYPE', expanded=True, color=QtGui.QColor("#000000"), font=font)
-        obj_name = self.addParent(parent, 'NAME', expanded=True, color=QtGui.QColor("#000000"), font=font)
-        dims = self.addParent(parent, 'Dimensions', expanded=True, color=QtGui.QColor("#000000"), font=font)
-        units = self.addParent(parent, 'Units', expanded=True, color=QtGui.QColor("#000000"), font=font)
+        obj_type = self.addParent(parent, _('TYPE'), expanded=True, color=QtGui.QColor("#000000"), font=font)
+        obj_name = self.addParent(parent, _('NAME'), expanded=True, color=QtGui.QColor("#000000"), font=font)
+        dims = self.addParent(parent, _('Dimensions'), expanded=True, color=QtGui.QColor("#000000"), font=font)
+        units = self.addParent(parent, _('Units'), expanded=True, color=QtGui.QColor("#000000"), font=font)
 
 
-        options = self.addParent(parent, 'Options', color=QtGui.QColor("#000000"), font=font)
+        options = self.addParent(parent, _('Options'), color=QtGui.QColor("#000000"), font=font)
         if obj.kind.lower() == 'gerber':
         if obj.kind.lower() == 'gerber':
-            apertures = self.addParent(parent, 'Apertures', expanded=True, color=QtGui.QColor("#000000"), font=font)
+            apertures = self.addParent(parent, _('Apertures'), expanded=True, color=QtGui.QColor("#000000"), font=font)
         else:
         else:
-            tools = self.addParent(parent, 'Tools', expanded=True, color=QtGui.QColor("#000000"), font=font)
+            tools = self.addParent(parent, _('Tools'), expanded=True, color=QtGui.QColor("#000000"), font=font)
 
 
         separator = self.addParent(parent, '')
         separator = self.addParent(parent, '')
 
 
-        self.addChild(obj_type, ['Object Type:', ('%s' % (obj.kind.capitalize()))], True)
+        self.addChild(obj_type, ['%s:' % _('Object Type'), ('%s' % (obj.kind.capitalize()))], True)
         try:
         try:
             self.addChild(obj_type,
             self.addChild(obj_type,
-                          ['Geo Type:', ('%s' % ({False: "Single-Geo", True: "Multi-Geo"}[obj.multigeo]))],
+                          ['%s:' % _('Geo Type'),
+                           ('%s' % ({False: _("Single-Geo"), True: _("Multi-Geo")}[obj.multigeo]))],
                           True)
                           True)
         except Exception as e:
         except Exception as e:
             log.debug("Properties.addItems() --> %s" % str(e))
             log.debug("Properties.addItems() --> %s" % str(e))
@@ -150,22 +156,44 @@ class Properties(FlatCAMTool):
         length = abs(xmax - xmin)
         length = abs(xmax - xmin)
         width = abs(ymax - ymin)
         width = abs(ymax - ymin)
 
 
-        self.addChild(dims, ['Length:', '%.4f %s' % (
+        self.addChild(dims, ['%s:' % _('Length'), '%.4f %s' % (
             length, self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower())], True)
             length, self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower())], True)
-        self.addChild(dims, ['Width:', '%.4f %s' % (
+        self.addChild(dims, ['%s:' % _('Width'), '%.4f %s' % (
             width, self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower())], True)
             width, self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower())], True)
+
+        # calculate and add box area
         if self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower() == 'mm':
         if self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower() == 'mm':
             area = (length * width) / 100
             area = (length * width) / 100
-            self.addChild(dims, ['Box Area:', '%.4f %s' % (area, 'cm2')], True)
+            self.addChild(dims, ['%s:' % _('Box Area'), '%.4f %s' % (area, 'cm2')], True)
         else:
         else:
             area = length * width
             area = length * width
-            self.addChild(dims, ['Box Area:', '%.4f %s' % (area, 'in2')], True)
+            self.addChild(dims, ['%s:' % _('Box Area'), '%.4f %s' % (area, 'in2')], True)
+
+        # calculate and add convex hull area
+        geo = obj.solid_geometry
+        if isinstance(geo, MultiPolygon):
+            env_obj = geo.convex_hull
+        elif (isinstance(geo, MultiPolygon) and len(geo) == 1) or \
+                (isinstance(geo, list) and len(geo) == 1) and isinstance(geo[0], Polygon):
+            env_obj = cascaded_union(self.bound_obj.solid_geometry)
+            env_obj = env_obj.convex_hull
+        else:
+            env_obj = cascaded_union(self.bound_obj.solid_geometry)
+            env_obj = env_obj.convex_hull
+
+        area_chull = env_obj.area
+        if self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower() == 'mm':
+            area_chull = area_chull / 100
+            self.addChild(dims, ['%s:' % _('Convex_Hull Area'), '%.4f %s' % (area_chull, 'cm2')], True)
+        else:
+            area_chull = area_chull
+            self.addChild(dims, ['%s:' % _('Convex_Hull Area'), '%.4f %s' % (area_chull, 'in2')], True)
 
 
         self.addChild(units,
         self.addChild(units,
                       ['FlatCAM units:',
                       ['FlatCAM units:',
                        {
                        {
-                           'in': 'Inch',
-                           'mm': 'Metric'
+                           'in': _('Inch'),
+                           'mm': _('Metric')
                        }
                        }
                        [str(self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower())]
                        [str(self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower())]
                        ],
                        ],
@@ -216,7 +244,7 @@ class Properties(FlatCAMTool):
                 geo_tool = self.addParent(tools, str(tool), expanded=True, color=QtGui.QColor("#000000"), font=font)
                 geo_tool = self.addParent(tools, str(tool), expanded=True, color=QtGui.QColor("#000000"), font=font)
                 for k, v in value.items():
                 for k, v in value.items():
                     if k == 'solid_geometry':
                     if k == 'solid_geometry':
-                        printed_value = 'Present' if v else 'None'
+                        printed_value = _('Present') if v else _('None')
                         self.addChild(geo_tool, [str(k), printed_value], True)
                         self.addChild(geo_tool, [str(k), printed_value], True)
                     elif k == 'data':
                     elif k == 'data':
                         tool_data = self.addParent(geo_tool, str(k).capitalize(),
                         tool_data = self.addParent(geo_tool, str(k).capitalize(),
@@ -230,13 +258,13 @@ class Properties(FlatCAMTool):
                 geo_tool = self.addParent(tools, str(tool), expanded=True, color=QtGui.QColor("#000000"), font=font)
                 geo_tool = self.addParent(tools, str(tool), expanded=True, color=QtGui.QColor("#000000"), font=font)
                 for k, v in value.items():
                 for k, v in value.items():
                     if k == 'solid_geometry':
                     if k == 'solid_geometry':
-                        printed_value = 'Present' if v else 'None'
+                        printed_value = _('Present') if v else _('None')
                         self.addChild(geo_tool, [str(k), printed_value], True)
                         self.addChild(geo_tool, [str(k), printed_value], True)
                     elif k == 'gcode':
                     elif k == 'gcode':
-                        printed_value = 'Present' if v != '' else 'None'
+                        printed_value = _('Present') if v != '' else _('None')
                         self.addChild(geo_tool, [str(k), printed_value], True)
                         self.addChild(geo_tool, [str(k), printed_value], True)
                     elif k == 'gcode_parsed':
                     elif k == 'gcode_parsed':
-                        printed_value = 'Present' if v else 'None'
+                        printed_value = _('Present') if v else _('None')
                         self.addChild(geo_tool, [str(k), printed_value], True)
                         self.addChild(geo_tool, [str(k), printed_value], True)
                     elif k == 'data':
                     elif k == 'data':
                         tool_data = self.addParent(geo_tool, str(k).capitalize(),
                         tool_data = self.addParent(geo_tool, str(k).capitalize(),

+ 26 - 21
flatcamTools/ToolSolderPaste.py

@@ -139,7 +139,7 @@ class SolderPaste(FlatCAMTool):
         grid0_1 = QtWidgets.QGridLayout()
         grid0_1 = QtWidgets.QGridLayout()
         self.layout.addLayout(grid0_1)
         self.layout.addLayout(grid0_1)
 
 
-        step1_lbl = QtWidgets.QLabel("<b>%s:</b>" % _('STEP 1:'))
+        step1_lbl = QtWidgets.QLabel("<b>%s:</b>" % _('STEP 1'))
         step1_lbl.setToolTip(
         step1_lbl.setToolTip(
             _("First step is to select a number of nozzle tools for usage\n"
             _("First step is to select a number of nozzle tools for usage\n"
               "and then optionally modify the GCode parameters bellow.")
               "and then optionally modify the GCode parameters bellow.")
@@ -163,7 +163,7 @@ class SolderPaste(FlatCAMTool):
 
 
         # Z dispense start
         # Z dispense start
         self.z_start_entry = FCEntry()
         self.z_start_entry = FCEntry()
-        self.z_start_label = QtWidgets.QLabel(_("Z Dispense Start:"))
+        self.z_start_label = QtWidgets.QLabel('%s:' % _("Z Dispense Start"))
         self.z_start_label.setToolTip(
         self.z_start_label.setToolTip(
             _("The height (Z) when solder paste dispensing starts.")
             _("The height (Z) when solder paste dispensing starts.")
         )
         )
@@ -171,7 +171,7 @@ class SolderPaste(FlatCAMTool):
 
 
         # Z dispense
         # Z dispense
         self.z_dispense_entry = FCEntry()
         self.z_dispense_entry = FCEntry()
-        self.z_dispense_label = QtWidgets.QLabel(_("Z Dispense:"))
+        self.z_dispense_label = QtWidgets.QLabel('%s:' % _("Z Dispense"))
         self.z_dispense_label.setToolTip(
         self.z_dispense_label.setToolTip(
             _("The height (Z) when doing solder paste dispensing.")
             _("The height (Z) when doing solder paste dispensing.")
         )
         )
@@ -179,7 +179,7 @@ class SolderPaste(FlatCAMTool):
 
 
         # Z dispense stop
         # Z dispense stop
         self.z_stop_entry = FCEntry()
         self.z_stop_entry = FCEntry()
-        self.z_stop_label = QtWidgets.QLabel(_("Z Dispense Stop:"))
+        self.z_stop_label = QtWidgets.QLabel('%s:' % _("Z Dispense Stop"))
         self.z_stop_label.setToolTip(
         self.z_stop_label.setToolTip(
             _("The height (Z) when solder paste dispensing stops.")
             _("The height (Z) when solder paste dispensing stops.")
         )
         )
@@ -187,7 +187,7 @@ class SolderPaste(FlatCAMTool):
 
 
         # Z travel
         # Z travel
         self.z_travel_entry = FCEntry()
         self.z_travel_entry = FCEntry()
-        self.z_travel_label = QtWidgets.QLabel(_("Z Travel:"))
+        self.z_travel_label = QtWidgets.QLabel('%s:' % _("Z Travel"))
         self.z_travel_label.setToolTip(
         self.z_travel_label.setToolTip(
            _("The height (Z) for travel between pads\n"
            _("The height (Z) for travel between pads\n"
              "(without dispensing solder paste).")
              "(without dispensing solder paste).")
@@ -196,7 +196,7 @@ class SolderPaste(FlatCAMTool):
 
 
         # Z toolchange location
         # Z toolchange location
         self.z_toolchange_entry = FCEntry()
         self.z_toolchange_entry = FCEntry()
-        self.z_toolchange_label = QtWidgets.QLabel(_("Z Toolchange:"))
+        self.z_toolchange_label = QtWidgets.QLabel('%s:' % _("Z Toolchange"))
         self.z_toolchange_label.setToolTip(
         self.z_toolchange_label.setToolTip(
            _("The height (Z) for tool (nozzle) change.")
            _("The height (Z) for tool (nozzle) change.")
         )
         )
@@ -204,7 +204,7 @@ class SolderPaste(FlatCAMTool):
 
 
         # X,Y Toolchange location
         # X,Y Toolchange location
         self.xy_toolchange_entry = FCEntry()
         self.xy_toolchange_entry = FCEntry()
-        self.xy_toolchange_label = QtWidgets.QLabel(_("XY Toolchange:"))
+        self.xy_toolchange_label = QtWidgets.QLabel('%s:' % _("Toolchange X-Y"))
         self.xy_toolchange_label.setToolTip(
         self.xy_toolchange_label.setToolTip(
             _("The X,Y location for tool (nozzle) change.\n"
             _("The X,Y location for tool (nozzle) change.\n"
               "The format is (x, y) where x and y are real numbers.")
               "The format is (x, y) where x and y are real numbers.")
@@ -213,7 +213,7 @@ class SolderPaste(FlatCAMTool):
 
 
         # Feedrate X-Y
         # Feedrate X-Y
         self.frxy_entry = FCEntry()
         self.frxy_entry = FCEntry()
-        self.frxy_label = QtWidgets.QLabel(_("Feedrate X-Y:"))
+        self.frxy_label = QtWidgets.QLabel('%s:' % _("Feedrate X-Y"))
         self.frxy_label.setToolTip(
         self.frxy_label.setToolTip(
            _("Feedrate (speed) while moving on the X-Y plane.")
            _("Feedrate (speed) while moving on the X-Y plane.")
         )
         )
@@ -221,7 +221,7 @@ class SolderPaste(FlatCAMTool):
 
 
         # Feedrate Z
         # Feedrate Z
         self.frz_entry = FCEntry()
         self.frz_entry = FCEntry()
-        self.frz_label = QtWidgets.QLabel(_("Feedrate Z:"))
+        self.frz_label = QtWidgets.QLabel('%s:' % _("Feedrate Z"))
         self.frz_label.setToolTip(
         self.frz_label.setToolTip(
             _("Feedrate (speed) while moving vertically\n"
             _("Feedrate (speed) while moving vertically\n"
               "(on Z plane).")
               "(on Z plane).")
@@ -230,7 +230,7 @@ class SolderPaste(FlatCAMTool):
 
 
         # Feedrate Z Dispense
         # Feedrate Z Dispense
         self.frz_dispense_entry = FCEntry()
         self.frz_dispense_entry = FCEntry()
-        self.frz_dispense_label = QtWidgets.QLabel(_("Feedrate Z Dispense:"))
+        self.frz_dispense_label = QtWidgets.QLabel('%s:' % _("Feedrate Z Dispense"))
         self.frz_dispense_label.setToolTip(
         self.frz_dispense_label.setToolTip(
            _("Feedrate (speed) while moving up vertically\n"
            _("Feedrate (speed) while moving up vertically\n"
              " to Dispense position (on Z plane).")
              " to Dispense position (on Z plane).")
@@ -239,7 +239,7 @@ class SolderPaste(FlatCAMTool):
 
 
         # Spindle Speed Forward
         # Spindle Speed Forward
         self.speedfwd_entry = FCEntry()
         self.speedfwd_entry = FCEntry()
-        self.speedfwd_label = QtWidgets.QLabel(_("Spindle Speed FWD:"))
+        self.speedfwd_label = QtWidgets.QLabel('%s:' % _("Spindle Speed FWD"))
         self.speedfwd_label.setToolTip(
         self.speedfwd_label.setToolTip(
            _("The dispenser speed while pushing solder paste\n"
            _("The dispenser speed while pushing solder paste\n"
              "through the dispenser nozzle.")
              "through the dispenser nozzle.")
@@ -248,7 +248,7 @@ class SolderPaste(FlatCAMTool):
 
 
         # Dwell Forward
         # Dwell Forward
         self.dwellfwd_entry = FCEntry()
         self.dwellfwd_entry = FCEntry()
-        self.dwellfwd_label = QtWidgets.QLabel(_("Dwell FWD:"))
+        self.dwellfwd_label = QtWidgets.QLabel('%s:' % _("Dwell FWD"))
         self.dwellfwd_label.setToolTip(
         self.dwellfwd_label.setToolTip(
             _("Pause after solder dispensing.")
             _("Pause after solder dispensing.")
         )
         )
@@ -256,7 +256,7 @@ class SolderPaste(FlatCAMTool):
 
 
         # Spindle Speed Reverse
         # Spindle Speed Reverse
         self.speedrev_entry = FCEntry()
         self.speedrev_entry = FCEntry()
-        self.speedrev_label = QtWidgets.QLabel(_("Spindle Speed REV:"))
+        self.speedrev_label = QtWidgets.QLabel('%s:' % _("Spindle Speed REV"))
         self.speedrev_label.setToolTip(
         self.speedrev_label.setToolTip(
            _("The dispenser speed while retracting solder paste\n"
            _("The dispenser speed while retracting solder paste\n"
              "through the dispenser nozzle.")
              "through the dispenser nozzle.")
@@ -265,7 +265,7 @@ class SolderPaste(FlatCAMTool):
 
 
         # Dwell Reverse
         # Dwell Reverse
         self.dwellrev_entry = FCEntry()
         self.dwellrev_entry = FCEntry()
-        self.dwellrev_label = QtWidgets.QLabel(_("Dwell REV:"))
+        self.dwellrev_label = QtWidgets.QLabel('%s:' % _("Dwell REV"))
         self.dwellrev_label.setToolTip(
         self.dwellrev_label.setToolTip(
             _("Pause after solder paste dispenser retracted,\n"
             _("Pause after solder paste dispenser retracted,\n"
               "to allow pressure equilibrium.")
               "to allow pressure equilibrium.")
@@ -273,7 +273,7 @@ class SolderPaste(FlatCAMTool):
         self.gcode_form_layout.addRow(self.dwellrev_label, self.dwellrev_entry)
         self.gcode_form_layout.addRow(self.dwellrev_label, self.dwellrev_entry)
 
 
         # Postprocessors
         # Postprocessors
-        pp_label = QtWidgets.QLabel(_('PostProcessors:'))
+        pp_label = QtWidgets.QLabel('%s:' % _('PostProcessor'))
         pp_label.setToolTip(
         pp_label.setToolTip(
             _("Files that control the GCode generation.")
             _("Files that control the GCode generation.")
         )
         )
@@ -303,7 +303,7 @@ class SolderPaste(FlatCAMTool):
         grid2 = QtWidgets.QGridLayout()
         grid2 = QtWidgets.QGridLayout()
         self.generation_box.addLayout(grid2)
         self.generation_box.addLayout(grid2)
 
 
-        step2_lbl = QtWidgets.QLabel("<b>%s</b>" % _('STEP 2:'))
+        step2_lbl = QtWidgets.QLabel("<b>%s:</b>" % _('STEP 2'))
         step2_lbl.setToolTip(
         step2_lbl.setToolTip(
             _("Second step is to create a solder paste dispensing\n"
             _("Second step is to create a solder paste dispensing\n"
               "geometry out of an Solder Paste Mask Gerber file.")
               "geometry out of an Solder Paste Mask Gerber file.")
@@ -321,7 +321,7 @@ class SolderPaste(FlatCAMTool):
         self.geo_obj_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
         self.geo_obj_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
         self.geo_obj_combo.setCurrentIndex(1)
         self.geo_obj_combo.setCurrentIndex(1)
 
 
-        self.geo_object_label = QtWidgets.QLabel(_("Geo Result:"))
+        self.geo_object_label = QtWidgets.QLabel('%s:' % _("Geo Result"))
         self.geo_object_label.setToolTip(
         self.geo_object_label.setToolTip(
            _("Geometry Solder Paste object.\n"
            _("Geometry Solder Paste object.\n"
              "The name of the object has to end in:\n"
              "The name of the object has to end in:\n"
@@ -332,7 +332,7 @@ class SolderPaste(FlatCAMTool):
         grid3 = QtWidgets.QGridLayout()
         grid3 = QtWidgets.QGridLayout()
         self.generation_box.addLayout(grid3)
         self.generation_box.addLayout(grid3)
 
 
-        step3_lbl = QtWidgets.QLabel("<b>%s</b>" % _('STEP 3:'))
+        step3_lbl = QtWidgets.QLabel("<b>%s:</b>" % _('STEP 3'))
         step3_lbl.setToolTip(
         step3_lbl.setToolTip(
            _("Third step is to select a solder paste dispensing geometry,\n"
            _("Third step is to select a solder paste dispensing geometry,\n"
              "and then generate a CNCJob object.\n\n"
              "and then generate a CNCJob object.\n\n"
@@ -354,7 +354,7 @@ class SolderPaste(FlatCAMTool):
         self.cnc_obj_combo.setRootModelIndex(self.app.collection.index(3, 0, QtCore.QModelIndex()))
         self.cnc_obj_combo.setRootModelIndex(self.app.collection.index(3, 0, QtCore.QModelIndex()))
         self.cnc_obj_combo.setCurrentIndex(1)
         self.cnc_obj_combo.setCurrentIndex(1)
 
 
-        self.cnc_object_label = QtWidgets.QLabel(_("CNC Result:"))
+        self.cnc_object_label = QtWidgets.QLabel('%s:' % _("CNC Result"))
         self.cnc_object_label.setToolTip(
         self.cnc_object_label.setToolTip(
            _("CNCJob Solder paste object.\n"
            _("CNCJob Solder paste object.\n"
              "In order to enable the GCode save section,\n"
              "In order to enable the GCode save section,\n"
@@ -378,7 +378,7 @@ class SolderPaste(FlatCAMTool):
              "on PCB pads, to a file.")
              "on PCB pads, to a file.")
         )
         )
 
 
-        step4_lbl = QtWidgets.QLabel("<b>%s</b>" % _('STEP 4:'))
+        step4_lbl = QtWidgets.QLabel("<b>%s:</b>" % _('STEP 4'))
         step4_lbl.setToolTip(
         step4_lbl.setToolTip(
            _("Fourth step (and last) is to select a CNCJob made from \n"
            _("Fourth step (and last) is to select a CNCJob made from \n"
              "a solder paste dispensing geometry, and then view/save it's GCode.")
              "a solder paste dispensing geometry, and then view/save it's GCode.")
@@ -436,7 +436,12 @@ class SolderPaste(FlatCAMTool):
             else:
             else:
                 try:
                 try:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
-                        self.app.ui.splitter.setSizes([0, 1])
+                        # if tab is populated with the tool but it does not have the focus, focus on it
+                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
+                            # focus on Tool Tab
+                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
+                        else:
+                            self.app.ui.splitter.setSizes([0, 1])
                 except AttributeError:
                 except AttributeError:
                     pass
                     pass
         else:
         else:

+ 67 - 44
flatcamTools/ToolSub.py

@@ -23,6 +23,8 @@ if '_' not in builtins.__dict__:
 
 
 class ToolSub(FlatCAMTool):
 class ToolSub(FlatCAMTool):
 
 
+    job_finished = QtCore.pyqtSignal(bool)
+
     toolName = _("Substract Tool")
     toolName = _("Substract Tool")
 
 
     def __init__(self, app):
     def __init__(self, app):
@@ -52,7 +54,7 @@ class ToolSub(FlatCAMTool):
         form_layout = QtWidgets.QFormLayout()
         form_layout = QtWidgets.QFormLayout()
         self.tools_box.addLayout(form_layout)
         self.tools_box.addLayout(form_layout)
 
 
-        self.gerber_title = QtWidgets.QLabel(_("<b>Gerber Objects</b>"))
+        self.gerber_title = QtWidgets.QLabel("<b>%s</b>" % _("Gerber Objects"))
         form_layout.addRow(self.gerber_title)
         form_layout.addRow(self.gerber_title)
 
 
         # Target Gerber Object
         # Target Gerber Object
@@ -61,7 +63,7 @@ class ToolSub(FlatCAMTool):
         self.target_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.target_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.target_gerber_combo.setCurrentIndex(1)
         self.target_gerber_combo.setCurrentIndex(1)
 
 
-        self.target_gerber_label = QtWidgets.QLabel(_("Target:"))
+        self.target_gerber_label = QtWidgets.QLabel('%s:' % _("Target"))
         self.target_gerber_label.setToolTip(
         self.target_gerber_label.setToolTip(
             _("Gerber object from which to substract\n"
             _("Gerber object from which to substract\n"
               "the substractor Gerber object.")
               "the substractor Gerber object.")
@@ -75,7 +77,7 @@ class ToolSub(FlatCAMTool):
         self.sub_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.sub_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.sub_gerber_combo.setCurrentIndex(1)
         self.sub_gerber_combo.setCurrentIndex(1)
 
 
-        self.sub_gerber_label = QtWidgets.QLabel(_("Substractor:"))
+        self.sub_gerber_label = QtWidgets.QLabel('%s:' % _("Substractor"))
         self.sub_gerber_label.setToolTip(
         self.sub_gerber_label.setToolTip(
             _("Gerber object that will be substracted\n"
             _("Gerber object that will be substracted\n"
               "from the target Gerber object.")
               "from the target Gerber object.")
@@ -98,7 +100,7 @@ class ToolSub(FlatCAMTool):
         form_geo_layout = QtWidgets.QFormLayout()
         form_geo_layout = QtWidgets.QFormLayout()
         self.tools_box.addLayout(form_geo_layout)
         self.tools_box.addLayout(form_geo_layout)
 
 
-        self.geo_title = QtWidgets.QLabel(_("<b>Geometry Objects</b>"))
+        self.geo_title = QtWidgets.QLabel("<b>%s</b>" % _("Geometry Objects"))
         form_geo_layout.addRow(self.geo_title)
         form_geo_layout.addRow(self.geo_title)
 
 
         # Target Geometry Object
         # Target Geometry Object
@@ -107,7 +109,7 @@ class ToolSub(FlatCAMTool):
         self.target_geo_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
         self.target_geo_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
         self.target_geo_combo.setCurrentIndex(1)
         self.target_geo_combo.setCurrentIndex(1)
 
 
-        self.target_geo_label = QtWidgets.QLabel(_("Target:"))
+        self.target_geo_label = QtWidgets.QLabel('%s:' % _("Target"))
         self.target_geo_label.setToolTip(
         self.target_geo_label.setToolTip(
             _("Geometry object from which to substract\n"
             _("Geometry object from which to substract\n"
               "the substractor Geometry object.")
               "the substractor Geometry object.")
@@ -121,7 +123,7 @@ class ToolSub(FlatCAMTool):
         self.sub_geo_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
         self.sub_geo_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
         self.sub_geo_combo.setCurrentIndex(1)
         self.sub_geo_combo.setCurrentIndex(1)
 
 
-        self.sub_geo_label = QtWidgets.QLabel(_("Substractor:"))
+        self.sub_geo_label = QtWidgets.QLabel('%s:' % _("Substractor"))
         self.sub_geo_label.setToolTip(
         self.sub_geo_label.setToolTip(
             _("Geometry object that will be substracted\n"
             _("Geometry object that will be substracted\n"
               "from the target Geometry object.")
               "from the target Geometry object.")
@@ -188,6 +190,7 @@ class ToolSub(FlatCAMTool):
         except (TypeError, AttributeError):
         except (TypeError, AttributeError):
             pass
             pass
         self.intersect_geo_btn.clicked.connect(self.on_geo_intersection_click)
         self.intersect_geo_btn.clicked.connect(self.on_geo_intersection_click)
+        self.job_finished.connect(self.on_job_finished)
 
 
     def install(self, icon=None, separator=None, **kwargs):
     def install(self, icon=None, separator=None, **kwargs):
         FlatCAMTool.install(self, icon, separator, shortcut='ALT+W', **kwargs)
         FlatCAMTool.install(self, icon, separator, shortcut='ALT+W', **kwargs)
@@ -202,7 +205,12 @@ class ToolSub(FlatCAMTool):
             else:
             else:
                 try:
                 try:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
-                        self.app.ui.splitter.setSizes([0, 1])
+                        # if tab is populated with the tool but it does not have the focus, focus on it
+                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
+                            # focus on Tool Tab
+                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
+                        else:
+                            self.app.ui.splitter.setSizes([0, 1])
                 except AttributeError:
                 except AttributeError:
                     pass
                     pass
         else:
         else:
@@ -286,7 +294,7 @@ class ToolSub(FlatCAMTool):
         for apid in self.target_grb_obj.apertures:
         for apid in self.target_grb_obj.apertures:
             self.promises.append(apid)
             self.promises.append(apid)
 
 
-        # start the QTimer to check for promises with 1 second period check
+        # start the QTimer to check for promises with 0.5 second period check
         self.periodic_check(500, reset=True)
         self.periodic_check(500, reset=True)
 
 
         for apid in self.target_grb_obj.apertures:
         for apid in self.target_grb_obj.apertures:
@@ -413,7 +421,10 @@ class ToolSub(FlatCAMTool):
             # cleanup
             # cleanup
             self.new_apertures.clear()
             self.new_apertures.clear()
             self.new_solid_geometry[:] = []
             self.new_solid_geometry[:] = []
-            self.sub_union[:] = []
+            try:
+                self.sub_union[:] = []
+            except TypeError:
+                self.sub_union = []
 
 
     def on_geo_intersection_click(self):
     def on_geo_intersection_click(self):
         # reset previous values
         # reset previous values
@@ -455,14 +466,14 @@ class ToolSub(FlatCAMTool):
             return
             return
 
 
         # create the target_options obj
         # create the target_options obj
-        self.target_options = {}
-        for opt in self.target_geo_obj.options:
-            if opt != 'name':
-                self.target_options[opt] = deepcopy(self.target_geo_obj.options[opt])
+        # self.target_options = dict()
+        # for k, v in self.target_geo_obj.options.items():
+        #     if k != 'name':
+        #         self.target_options[k] = v
 
 
         # crate the new_tools dict structure
         # crate the new_tools dict structure
         for tool in self.target_geo_obj.tools:
         for tool in self.target_geo_obj.tools:
-            self.new_tools[tool] = {}
+            self.new_tools[tool] = dict()
             for key in self.target_geo_obj.tools[tool]:
             for key in self.target_geo_obj.tools[tool]:
                 if key == 'solid_geometry':
                 if key == 'solid_geometry':
                     self.new_tools[tool][key] = []
                     self.new_tools[tool][key] = []
@@ -514,16 +525,14 @@ class ToolSub(FlatCAMTool):
                         if isinstance(geo_elem, Polygon):
                         if isinstance(geo_elem, Polygon):
                             for ring in self.poly2rings(geo_elem):
                             for ring in self.poly2rings(geo_elem):
                                 new_geo = ring.difference(self.sub_union)
                                 new_geo = ring.difference(self.sub_union)
-                                if new_geo:
-                                    if not new_geo.is_empty:
-                                        new_geometry.append(new_geo)
+                                if new_geo and not new_geo.is_empty:
+                                    new_geometry.append(new_geo)
                         elif isinstance(geo_elem, MultiPolygon):
                         elif isinstance(geo_elem, MultiPolygon):
                             for poly in geo_elem:
                             for poly in geo_elem:
                                 for ring in self.poly2rings(poly):
                                 for ring in self.poly2rings(poly):
                                     new_geo = ring.difference(self.sub_union)
                                     new_geo = ring.difference(self.sub_union)
-                                    if new_geo:
-                                        if not new_geo.is_empty:
-                                            new_geometry.append(new_geo)
+                                    if new_geo and not new_geo.is_empty:
+                                        new_geometry.append(new_geo)
                         elif isinstance(geo_elem, LineString):
                         elif isinstance(geo_elem, LineString):
                             new_geo = geo_elem.difference(self.sub_union)
                             new_geo = geo_elem.difference(self.sub_union)
                             if new_geo:
                             if new_geo:
@@ -532,9 +541,8 @@ class ToolSub(FlatCAMTool):
                         elif isinstance(geo_elem, MultiLineString):
                         elif isinstance(geo_elem, MultiLineString):
                             for line_elem in geo_elem:
                             for line_elem in geo_elem:
                                 new_geo = line_elem.difference(self.sub_union)
                                 new_geo = line_elem.difference(self.sub_union)
-                                if new_geo:
-                                    if not new_geo.is_empty:
-                                        new_geometry.append(new_geo)
+                                if new_geo and not new_geo.is_empty:
+                                    new_geometry.append(new_geo)
                 except TypeError:
                 except TypeError:
                     if isinstance(geo, Polygon):
                     if isinstance(geo, Polygon):
                         for ring in self.poly2rings(geo):
                         for ring in self.poly2rings(geo):
@@ -544,15 +552,13 @@ class ToolSub(FlatCAMTool):
                                     new_geometry.append(new_geo)
                                     new_geometry.append(new_geo)
                     elif isinstance(geo, LineString):
                     elif isinstance(geo, LineString):
                         new_geo = geo.difference(self.sub_union)
                         new_geo = geo.difference(self.sub_union)
-                        if new_geo:
-                            if not new_geo.is_empty:
-                                new_geometry.append(new_geo)
+                        if new_geo and not new_geo.is_empty:
+                            new_geometry.append(new_geo)
                     elif isinstance(geo, MultiLineString):
                     elif isinstance(geo, MultiLineString):
                         for line_elem in geo:
                         for line_elem in geo:
                             new_geo = line_elem.difference(self.sub_union)
                             new_geo = line_elem.difference(self.sub_union)
-                            if new_geo:
-                                if not new_geo.is_empty:
-                                    new_geometry.append(new_geo)
+                            if new_geo and not new_geo.is_empty:
+                                new_geometry.append(new_geo)
 
 
         if new_geometry:
         if new_geometry:
             if tool == "single":
             if tool == "single":
@@ -575,10 +581,14 @@ class ToolSub(FlatCAMTool):
         log.debug("Promise fulfilled: %s" % str(tool))
         log.debug("Promise fulfilled: %s" % str(tool))
 
 
     def new_geo_object(self, outname):
     def new_geo_object(self, outname):
+        geo_name = outname
         def obj_init(geo_obj, app_obj):
         def obj_init(geo_obj, app_obj):
 
 
-            geo_obj.options = deepcopy(self.target_options)
-            geo_obj.options['name'] = outname
+            # geo_obj.options = self.target_options
+            # create the target_options obj
+            for k, v in self.target_geo_obj.options.items():
+                geo_obj.options[k] = v
+            geo_obj.options['name'] = geo_name
 
 
             if self.target_geo_obj.multigeo:
             if self.target_geo_obj.multigeo:
                 geo_obj.tools = deepcopy(self.new_tools)
                 geo_obj.tools = deepcopy(self.new_tools)
@@ -593,6 +603,7 @@ class ToolSub(FlatCAMTool):
                         geo_obj.tools[tool]['solid_geometry'] = deepcopy(self.new_solid_geometry)
                         geo_obj.tools[tool]['solid_geometry'] = deepcopy(self.new_solid_geometry)
                 except Exception as e:
                 except Exception as e:
                     log.debug("ToolSub.new_geo_object() --> %s" % str(e))
                     log.debug("ToolSub.new_geo_object() --> %s" % str(e))
+                geo_obj.multigeo = False
 
 
         with self.app.proc_container.new(_("Generating new object ...")):
         with self.app.proc_container.new(_("Generating new object ...")):
             ret = self.app.new_object('geometry', outname, obj_init, autoselected=False)
             ret = self.app.new_object('geometry', outname, obj_init, autoselected=False)
@@ -607,7 +618,7 @@ class ToolSub(FlatCAMTool):
             # cleanup
             # cleanup
             self.new_tools.clear()
             self.new_tools.clear()
             self.new_solid_geometry[:] = []
             self.new_solid_geometry[:] = []
-            self.sub_union[:] = []
+            self.sub_union = []
 
 
     def periodic_check(self, check_period, reset=False):
     def periodic_check(self, check_period, reset=False):
         """
         """
@@ -645,27 +656,39 @@ class ToolSub(FlatCAMTool):
         try:
         try:
             if not self.promises:
             if not self.promises:
                 self.check_thread.stop()
                 self.check_thread.stop()
-                if self.sub_type == "gerber":
-                    outname = self.target_gerber_combo.currentText() + '_sub'
-
-                    # intersection jobs finished, start the creation of solid_geometry
-                    self.app.worker_task.emit({'fcn': self.new_gerber_object,
-                                               'params': [outname]})
-                else:
-                    outname = self.target_geo_combo.currentText() + '_sub'
-
-                    # intersection jobs finished, start the creation of solid_geometry
-                    self.app.worker_task.emit({'fcn': self.new_geo_object,
-                                               'params': [outname]})
+                self.job_finished.emit(True)
 
 
                 # reset the type of substraction for next time
                 # reset the type of substraction for next time
                 self.sub_type = None
                 self.sub_type = None
 
 
                 log.debug("ToolSub --> Periodic check finished.")
                 log.debug("ToolSub --> Periodic check finished.")
         except Exception as e:
         except Exception as e:
+            self.job_finished.emit(False)
             log.debug("ToolSub().periodic_check_handler() --> %s" % str(e))
             log.debug("ToolSub().periodic_check_handler() --> %s" % str(e))
             traceback.print_exc()
             traceback.print_exc()
 
 
+    def on_job_finished(self, succcess):
+        """
+
+        :param succcess: boolean, this parameter signal if all the apertures were processed
+        :return: None
+        """
+        if succcess is True:
+            if self.sub_type == "gerber":
+                outname = self.target_gerber_combo.currentText() + '_sub'
+
+                # intersection jobs finished, start the creation of solid_geometry
+                self.app.worker_task.emit({'fcn': self.new_gerber_object,
+                                           'params': [outname]})
+            else:
+                outname = self.target_geo_combo.currentText() + '_sub'
+
+                # intersection jobs finished, start the creation of solid_geometry
+                self.app.worker_task.emit({'fcn': self.new_geo_object,
+                                           'params': [outname]})
+        else:
+            self.app.inform.emit(_('[ERROR_NOTCL] Generating new object failed.'))
+
     def reset_fields(self):
     def reset_fields(self):
         self.target_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.target_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.sub_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.sub_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))

+ 22 - 16
flatcamTools/ToolTransform.py

@@ -65,7 +65,7 @@ class ToolTransform(FlatCAMTool):
         self.transform_lay.addLayout(form_layout)
         self.transform_lay.addLayout(form_layout)
         form_child = QtWidgets.QHBoxLayout()
         form_child = QtWidgets.QHBoxLayout()
 
 
-        self.rotate_label = QtWidgets.QLabel(_("Angle:"))
+        self.rotate_label = QtWidgets.QLabel('%s:' % _("Angle"))
         self.rotate_label.setToolTip(
         self.rotate_label.setToolTip(
             _("Angle for Rotation action, in degrees.\n"
             _("Angle for Rotation action, in degrees.\n"
               "Float number between -360 and 359.\n"
               "Float number between -360 and 359.\n"
@@ -104,7 +104,7 @@ class ToolTransform(FlatCAMTool):
         form1_child_1 = QtWidgets.QHBoxLayout()
         form1_child_1 = QtWidgets.QHBoxLayout()
         form1_child_2 = QtWidgets.QHBoxLayout()
         form1_child_2 = QtWidgets.QHBoxLayout()
 
 
-        self.skewx_label = QtWidgets.QLabel(_("Angle X:"))
+        self.skewx_label = QtWidgets.QLabel('%s:' % _("Skew_X angle"))
         self.skewx_label.setToolTip(
         self.skewx_label.setToolTip(
             _("Angle for Skew action, in degrees.\n"
             _("Angle for Skew action, in degrees.\n"
               "Float number between -360 and 359.")
               "Float number between -360 and 359.")
@@ -122,7 +122,7 @@ class ToolTransform(FlatCAMTool):
               "the bounding box for all selected objects."))
               "the bounding box for all selected objects."))
         self.skewx_button.setMinimumWidth(90)
         self.skewx_button.setMinimumWidth(90)
 
 
-        self.skewy_label = QtWidgets.QLabel(_("Angle Y:"))
+        self.skewy_label = QtWidgets.QLabel('%s:' % _("Skew_Y angle"))
         self.skewy_label.setToolTip(
         self.skewy_label.setToolTip(
             _("Angle for Skew action, in degrees.\n"
             _("Angle for Skew action, in degrees.\n"
               "Float number between -360 and 359.")
               "Float number between -360 and 359.")
@@ -161,9 +161,9 @@ class ToolTransform(FlatCAMTool):
         form2_child_1 = QtWidgets.QHBoxLayout()
         form2_child_1 = QtWidgets.QHBoxLayout()
         form2_child_2 = QtWidgets.QHBoxLayout()
         form2_child_2 = QtWidgets.QHBoxLayout()
 
 
-        self.scalex_label = QtWidgets.QLabel(_("Factor X:"))
+        self.scalex_label = QtWidgets.QLabel('%s:' % _("Scale_X factor"))
         self.scalex_label.setToolTip(
         self.scalex_label.setToolTip(
-            _("Factor for Scale action over X axis.")
+            _("Factor for scaling on X axis.")
         )
         )
         self.scalex_label.setMinimumWidth(70)
         self.scalex_label.setMinimumWidth(70)
         self.scalex_entry = FCEntry()
         self.scalex_entry = FCEntry()
@@ -178,9 +178,9 @@ class ToolTransform(FlatCAMTool):
               "the Scale reference checkbox state."))
               "the Scale reference checkbox state."))
         self.scalex_button.setMinimumWidth(90)
         self.scalex_button.setMinimumWidth(90)
 
 
-        self.scaley_label = QtWidgets.QLabel(_("Factor Y:"))
+        self.scaley_label = QtWidgets.QLabel('%s:' % _("Scale_Y factor"))
         self.scaley_label.setToolTip(
         self.scaley_label.setToolTip(
-            _("Factor for Scale action over Y axis.")
+            _("Factor for scaling on Y axis.")
         )
         )
         self.scaley_label.setMinimumWidth(70)
         self.scaley_label.setMinimumWidth(70)
         self.scaley_entry = FCEntry()
         self.scaley_entry = FCEntry()
@@ -200,12 +200,13 @@ class ToolTransform(FlatCAMTool):
         self.scale_link_cb.setText(_("Link"))
         self.scale_link_cb.setText(_("Link"))
         self.scale_link_cb.setToolTip(
         self.scale_link_cb.setToolTip(
             _("Scale the selected object(s)\n"
             _("Scale the selected object(s)\n"
-              "using the Scale Factor X for both axis."))
+              "using the Scale_X factor for both axis.")
+        )
         self.scale_link_cb.setMinimumWidth(70)
         self.scale_link_cb.setMinimumWidth(70)
 
 
         self.scale_zero_ref_cb = FCCheckBox()
         self.scale_zero_ref_cb = FCCheckBox()
         self.scale_zero_ref_cb.set_value(True)
         self.scale_zero_ref_cb.set_value(True)
-        self.scale_zero_ref_cb.setText(_("Scale Reference"))
+        self.scale_zero_ref_cb.setText('%s' % _("Scale Reference"))
         self.scale_zero_ref_cb.setToolTip(
         self.scale_zero_ref_cb.setToolTip(
             _("Scale the selected object(s)\n"
             _("Scale the selected object(s)\n"
               "using the origin reference when checked,\n"
               "using the origin reference when checked,\n"
@@ -235,9 +236,9 @@ class ToolTransform(FlatCAMTool):
         form3_child_1 = QtWidgets.QHBoxLayout()
         form3_child_1 = QtWidgets.QHBoxLayout()
         form3_child_2 = QtWidgets.QHBoxLayout()
         form3_child_2 = QtWidgets.QHBoxLayout()
 
 
-        self.offx_label = QtWidgets.QLabel(_("Value X:"))
+        self.offx_label = QtWidgets.QLabel('%s:' % _("Offset_X val"))
         self.offx_label.setToolTip(
         self.offx_label.setToolTip(
-            _("Value for Offset action on X axis.")
+            _("Distance to offset on X axis. In current units.")
         )
         )
         self.offx_label.setMinimumWidth(70)
         self.offx_label.setMinimumWidth(70)
         self.offx_entry = FCEntry()
         self.offx_entry = FCEntry()
@@ -252,9 +253,9 @@ class ToolTransform(FlatCAMTool):
               "the bounding box for all selected objects.\n"))
               "the bounding box for all selected objects.\n"))
         self.offx_button.setMinimumWidth(90)
         self.offx_button.setMinimumWidth(90)
 
 
-        self.offy_label = QtWidgets.QLabel(_("Value Y:"))
+        self.offy_label = QtWidgets.QLabel('%s:' % _("Offset_Y val"))
         self.offy_label.setToolTip(
         self.offy_label.setToolTip(
-            _("Value for Offset action on Y axis.")
+            _("Distance to offset on Y axis. In current units.")
         )
         )
         self.offy_label.setMinimumWidth(70)
         self.offy_label.setMinimumWidth(70)
         self.offy_entry = FCEntry()
         self.offy_entry = FCEntry()
@@ -309,7 +310,7 @@ class ToolTransform(FlatCAMTool):
 
 
         self.flip_ref_cb = FCCheckBox()
         self.flip_ref_cb = FCCheckBox()
         self.flip_ref_cb.set_value(True)
         self.flip_ref_cb.set_value(True)
-        self.flip_ref_cb.setText(_("Ref Pt"))
+        self.flip_ref_cb.setText('%s' % _("Mirror Reference"))
         self.flip_ref_cb.setToolTip(
         self.flip_ref_cb.setToolTip(
             _("Flip the selected object(s)\n"
             _("Flip the selected object(s)\n"
               "around the point in Point Entry Field.\n"
               "around the point in Point Entry Field.\n"
@@ -322,7 +323,7 @@ class ToolTransform(FlatCAMTool):
               "Point Entry field and click Flip on X(Y)"))
               "Point Entry field and click Flip on X(Y)"))
         self.flip_ref_cb.setMinimumWidth(70)
         self.flip_ref_cb.setMinimumWidth(70)
 
 
-        self.flip_ref_label = QtWidgets.QLabel(_("Point:"))
+        self.flip_ref_label = QtWidgets.QLabel('%s:' % _(" Mirror Ref. Point"))
         self.flip_ref_label.setToolTip(
         self.flip_ref_label.setToolTip(
             _("Coordinates in format (x, y) used as reference for mirroring.\n"
             _("Coordinates in format (x, y) used as reference for mirroring.\n"
               "The 'x' in (x, y) will be used when using Flip on X and\n"
               "The 'x' in (x, y) will be used when using Flip on X and\n"
@@ -384,7 +385,12 @@ class ToolTransform(FlatCAMTool):
             else:
             else:
                 try:
                 try:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
                     if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
-                        self.app.ui.splitter.setSizes([0, 1])
+                        # if tab is populated with the tool but it does not have the focus, focus on it
+                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
+                            # focus on Tool Tab
+                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
+                        else:
+                            self.app.ui.splitter.setSizes([0, 1])
                 except AttributeError:
                 except AttributeError:
                     pass
                     pass
         else:
         else:

BIN
locale/de/LC_MESSAGES/strings.mo


Plik diff jest za duży
+ 207 - 201
locale/de/LC_MESSAGES/strings.po


BIN
locale/en/LC_MESSAGES/strings.mo


Plik diff jest za duży
+ 213 - 207
locale/en/LC_MESSAGES/strings.po


BIN
locale/es/LC_MESSAGES/strings.mo


Plik diff jest za duży
+ 207 - 201
locale/es/LC_MESSAGES/strings.po


BIN
locale/pt_BR/LC_MESSAGES/strings.mo


Plik diff jest za duży
+ 207 - 201
locale/pt_BR/LC_MESSAGES/strings.po


BIN
locale/ro/LC_MESSAGES/strings.mo


Plik diff jest za duży
+ 213 - 207
locale/ro/LC_MESSAGES/strings.po


BIN
locale/ru/LC_MESSAGES/strings.mo


Plik diff jest za duży
+ 212 - 206
locale/ru/LC_MESSAGES/strings.po


Plik diff jest za duży
+ 268 - 254
locale_template/strings.pot


+ 1 - 1
tclCommands/TclCommandFollow.py

@@ -57,7 +57,7 @@ class TclCommandFollow(TclCommandSignaled):
 
 
         del args['name']
         del args['name']
         try:
         try:
-            obj.follow(**args)
+            obj.follow_geo(**args)
         except Exception as e:
         except Exception as e:
             return "Operation failed: %s" % str(e)
             return "Operation failed: %s" % str(e)
 
 

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików