David Robertson 5 лет назад
Родитель
Сommit
1fdd0c26e0
52 измененных файлов с 3037 добавлено и 2728 удалено
  1. 21 0
      CHANGELOG.md
  2. 108 123
      FlatCAMApp.py
  3. 69 0
      assets/examples/files/test.gbr
  4. 28 0
      assets/examples/files/test.txt
  5. 30 0
      assets/examples/files/test_1.gbr
  6. 19 0
      assets/examples/open_file.FlatScript
  7. BIN
      assets/resources/dark_resources/flatcam_icon32_green.png
  8. BIN
      assets/resources/dark_resources/snap_16.png
  9. BIN
      assets/resources/dark_resources/snap_filled_16.png
  10. BIN
      assets/resources/snap_16.png
  11. BIN
      assets/resources/snap_filled_16.png
  12. 9 14
      camlib.py
  13. 1 0
      defaults.py
  14. 3 3
      flatcamEditors/FlatCAMExcEditor.py
  15. 1 1
      flatcamEditors/FlatCAMGeoEditor.py
  16. 53 5
      flatcamEditors/FlatCAMGrbEditor.py
  17. 2 2
      flatcamEditors/FlatCAMTextEditor.py
  18. 30 5
      flatcamGUI/FlatCAMGUI.py
  19. 1 1
      flatcamGUI/preferences/PreferencesUIManager.py
  20. 16 1
      flatcamGUI/preferences/general/GeneralAppPrefGroupUI.py
  21. 1 1
      flatcamGUI/preferences/general/GeneralGUIPrefGroupUI.py
  22. 1 1
      flatcamObjects/FlatCAMCNCJob.py
  23. 26 0
      flatcamObjects/FlatCAMGeometry.py
  24. 33 6
      flatcamObjects/FlatCAMScript.py
  25. 1 1
      flatcamParsers/ParseExcellon.py
  26. 106 9
      flatcamTools/ToolCutOut.py
  27. 1 1
      flatcamTools/ToolFilm.py
  28. 78 79
      flatcamTools/ToolPanelize.py
  29. 5 10
      flatcamTools/ToolPcbWizard.py
  30. 1 1
      flatcamTools/ToolSolderPaste.py
  31. 1 1
      flatcamTools/ToolSub.py
  32. BIN
      locale/de/LC_MESSAGES/strings.mo
  33. 206 200
      locale/de/LC_MESSAGES/strings.po
  34. BIN
      locale/en/LC_MESSAGES/strings.mo
  35. 207 201
      locale/en/LC_MESSAGES/strings.po
  36. BIN
      locale/es/LC_MESSAGES/strings.mo
  37. 206 200
      locale/es/LC_MESSAGES/strings.po
  38. BIN
      locale/fr/LC_MESSAGES/strings.mo
  39. 206 200
      locale/fr/LC_MESSAGES/strings.po
  40. BIN
      locale/hu/LC_MESSAGES/strings.mo
  41. 222 301
      locale/hu/LC_MESSAGES/strings.po
  42. BIN
      locale/it/LC_MESSAGES/strings.mo
  43. 720 754
      locale/it/LC_MESSAGES/strings.po
  44. BIN
      locale/pt_BR/LC_MESSAGES/strings.mo
  45. 206 200
      locale/pt_BR/LC_MESSAGES/strings.po
  46. BIN
      locale/ro/LC_MESSAGES/strings.mo
  47. 206 200
      locale/ro/LC_MESSAGES/strings.po
  48. BIN
      locale/ru/LC_MESSAGES/strings.mo
  49. 206 200
      locale/ru/LC_MESSAGES/strings.po
  50. 5 5
      locale_template/strings.pot
  51. 1 1
      tclCommands/TclCommandJoinExcellon.py
  52. 1 1
      tclCommands/TclCommandJoinGeometry.py

+ 21 - 0
CHANGELOG.md

@@ -7,6 +7,27 @@ CHANGELOG for FlatCAM beta
 
 
 =================================================
 =================================================
 
 
+2.05.2020
+
+- changed the icons for the grid snap in the status bar
+- moved some of the methods from FlatCAMApp.App to flatcamGUI.FlatCAMGUI class
+- fixed bug in Gerber Editor in which the units conversion wasn't calculated correct
+- fixed bug in Gerber Editor in which the QThread that is started on object edit was not stopped at clean up stage
+- fixed bug in Gerber Editor that kept all the apertures (including the geometry) of a previously edited object that was not saved after edit
+- modified the Cutout Tool to generate multi-geo objects therefore the set geometry parameters will populate the Geometry Object UI
+- modified the Panelize Tool to optimize the output from Cutout Tool such that there are no longer overlapping cuts
+- some string corrections
+- updated the Italian translation done by user @pcb-hobbyst
+- RELEASE 8.992
+
+01.05.2020
+
+- added some ToolTips (strings needed to be translated too) for the Cut Z entry in Geometry Object UI that explain why is sometime disabled and reason for it's value (sometime is zero)
+- solve parenting issues when trying to load a FlatScript from Menu -> File -> Scripting
+- added a first new example script and added some files to work with
+- added a new parameter that will store the home folder of the FlatCAM installation so we can access the example folder
+- added in Gerber editor a method for zoom fit that takes into consideration the current geometry of the edited object
+
 30.04.2020 
 30.04.2020 
 
 
 - made some corrections - due of recent refactoring PyCharm reported errors all over (not correct but it made programming difficult)
 - made some corrections - due of recent refactoring PyCharm reported errors all over (not correct but it made programming difficult)

+ 108 - 123
FlatCAMApp.py

@@ -163,7 +163,7 @@ class App(QtCore.QObject):
     # ################################### Version and VERSION DATE ##################################################
     # ################################### Version and VERSION DATE ##################################################
     # ###############################################################################################################
     # ###############################################################################################################
     version = 8.992
     version = 8.992
-    version_date = "2020/05/01"
+    version_date = "2020/05/03"
     beta = True
     beta = True
     engine = '3D'
     engine = '3D'
 
 
@@ -432,6 +432,9 @@ class App(QtCore.QObject):
         # ################################# DEFAULTS - PREFERENCES STORAGE ###########################################
         # ################################# DEFAULTS - PREFERENCES STORAGE ###########################################
         # ############################################################################################################
         # ############################################################################################################
         self.defaults = FlatCAMDefaults()
         self.defaults = FlatCAMDefaults()
+
+        self.defaults["root_folder_path"] = self.app_home
+
         current_defaults_path = os.path.join(self.data_path, "current_defaults.FlatConfig")
         current_defaults_path = os.path.join(self.data_path, "current_defaults.FlatConfig")
         if user_defaults:
         if user_defaults:
             self.defaults.load(filename=current_defaults_path)
             self.defaults.load(filename=current_defaults_path)
@@ -503,7 +506,6 @@ class App(QtCore.QObject):
         QtCore.QObject.__init__(self)
         QtCore.QObject.__init__(self)
 
 
         self.ui = FlatCAMGUI(self)
         self.ui = FlatCAMGUI(self)
-        self.on_grid_snap_triggered(state=True)
 
 
         theme_settings = QtCore.QSettings("Open Source", "FlatCAM")
         theme_settings = QtCore.QSettings("Open Source", "FlatCAM")
         if theme_settings.contains("theme"):
         if theme_settings.contains("theme"):
@@ -1071,9 +1073,6 @@ class App(QtCore.QObject):
         # signal emitted when a tab is closed in the Plot Area
         # signal emitted when a tab is closed in the Plot Area
         self.ui.plot_tab_area.tab_closed_signal.connect(self.on_plot_area_tab_closed)
         self.ui.plot_tab_area.tab_closed_signal.connect(self.on_plot_area_tab_closed)
 
 
-        self.ui.grid_snap_btn.triggered.connect(self.on_grid_snap_triggered)
-        self.ui.snap_infobar_label.clicked.connect(self.on_grid_icon_snap_clicked)
-
         # signal to close the application
         # signal to close the application
         self.close_app_signal.connect(self.kill_app)
         self.close_app_signal.connect(self.kill_app)
         # ################################# FINISHED CONNECTING SIGNALS #############################################
         # ################################# FINISHED CONNECTING SIGNALS #############################################
@@ -2908,45 +2907,37 @@ class App(QtCore.QObject):
 
 
         self.new_object('gerber', 'new_grb', initialize, plot=False)
         self.new_object('gerber', 'new_grb', initialize, plot=False)
 
 
-    def new_script_object(self, name=None, text=None):
+    def new_script_object(self):
         """
         """
         Creates a new, blank TCL Script object.
         Creates a new, blank TCL Script object.
-        :param name: a name for the new object
-        :param text: pass a source file to the newly created script to be loaded in it
+
         :return: None
         :return: None
         """
         """
         self.defaults.report_usage("new_script_object()")
         self.defaults.report_usage("new_script_object()")
 
 
-        if text is not None:
-            new_source_file = text
-        else:
-            # commands_list = "# AddCircle, AddPolygon, AddPolyline, AddRectangle, AlignDrill, " \
-            #                 "AlignDrillGrid, Bbox, Bounds, ClearShell, CopperClear,\n" \
-            #                 "# Cncjob, Cutout, Delete, Drillcncjob, ExportDXF, ExportExcellon, ExportGcode,\n" \
-            #                 "# ExportGerber, ExportSVG, Exteriors, Follow, GeoCutout, GeoUnion, GetNames,\n" \
-            #                 "# GetSys, ImportSvg, Interiors, Isolate, JoinExcellon, JoinGeometry, " \
-            #                 "ListSys, MillDrills,\n" \
-            #                 "# MillSlots, Mirror, New, NewExcellon, NewGeometry, NewGerber, Nregions, " \
-            #                 "Offset, OpenExcellon, OpenGCode, OpenGerber, OpenProject,\n" \
-            #                 "# Options, Paint, Panelize, PlotAl, PlotObjects, SaveProject, " \
-            #                 "SaveSys, Scale, SetActive, SetSys, SetOrigin, Skew, SubtractPoly,\n" \
-            #                 "# SubtractRectangle, Version, WriteGCode\n"
-
-            new_source_file = '# %s\n' % _('CREATE A NEW FLATCAM TCL SCRIPT') + \
-                              '# %s:\n' % _('TCL Tutorial is here') + \
-                              '# https://www.tcl.tk/man/tcl8.5/tutorial/tcltutorial.html\n' + '\n\n' + \
-                              '# %s:\n' % _("FlatCAM commands list")
-            new_source_file += '# %s\n\n' % _("Type >help< followed by Run Code for a list of FlatCAM Tcl Commands "
-                                              "(displayed in Tcl Shell).")
+        # commands_list = "# AddCircle, AddPolygon, AddPolyline, AddRectangle, AlignDrill, " \
+        #                 "AlignDrillGrid, Bbox, Bounds, ClearShell, CopperClear,\n" \
+        #                 "# Cncjob, Cutout, Delete, Drillcncjob, ExportDXF, ExportExcellon, ExportGcode,\n" \
+        #                 "# ExportGerber, ExportSVG, Exteriors, Follow, GeoCutout, GeoUnion, GetNames,\n" \
+        #                 "# GetSys, ImportSvg, Interiors, Isolate, JoinExcellon, JoinGeometry, " \
+        #                 "ListSys, MillDrills,\n" \
+        #                 "# MillSlots, Mirror, New, NewExcellon, NewGeometry, NewGerber, Nregions, " \
+        #                 "Offset, OpenExcellon, OpenGCode, OpenGerber, OpenProject,\n" \
+        #                 "# Options, Paint, Panelize, PlotAl, PlotObjects, SaveProject, " \
+        #                 "SaveSys, Scale, SetActive, SetSys, SetOrigin, Skew, SubtractPoly,\n" \
+        #                 "# SubtractRectangle, Version, WriteGCode\n"
+
+        new_source_file = '# %s\n' % _('CREATE A NEW FLATCAM TCL SCRIPT') + \
+                          '# %s:\n' % _('TCL Tutorial is here') + \
+                          '# https://www.tcl.tk/man/tcl8.5/tutorial/tcltutorial.html\n' + '\n\n' + \
+                          '# %s:\n' % _("FlatCAM commands list")
+        new_source_file += '# %s\n\n' % _("Type >help< followed by Run Code for a list of FlatCAM Tcl Commands "
+                                          "(displayed in Tcl Shell).")
 
 
         def initialize(obj, app):
         def initialize(obj, app):
             obj.source_file = deepcopy(new_source_file)
             obj.source_file = deepcopy(new_source_file)
 
 
-        if name is None:
-            outname = 'new_script'
-        else:
-            outname = name
-
+        outname = 'new_script'
         self.new_object('script', outname, initialize, plot=False)
         self.new_object('script', outname, initialize, plot=False)
 
 
     def new_document_object(self):
     def new_document_object(self):
@@ -3268,7 +3259,7 @@ class App(QtCore.QObject):
                 self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "<marius_adrian@yahoo.com>"), 4, 2)
                 self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "<marius_adrian@yahoo.com>"), 4, 2)
                 self.prog_grid_lay.addWidget(QtWidgets.QLabel(''), 5, 0)
                 self.prog_grid_lay.addWidget(QtWidgets.QLabel(''), 5, 0)
 
 
-                self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Alex Lazar"), 6, 0)
+                self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "David Robertson"), 6, 0)
                 self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Matthieu Berthomé"), 7, 0)
                 self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Matthieu Berthomé"), 7, 0)
                 self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Mike Evans"), 8, 0)
                 self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Mike Evans"), 8, 0)
                 self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Victor Benso"), 9, 0)
                 self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Victor Benso"), 9, 0)
@@ -3300,6 +3291,7 @@ class App(QtCore.QObject):
 
 
                 self.prog_grid_lay.addWidget(QtWidgets.QLabel(''), 63, 0)
                 self.prog_grid_lay.addWidget(QtWidgets.QLabel(''), 63, 0)
 
 
+                self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Alex Lazar"), 64, 0)
                 self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Chris Breneman"), 65, 0)
                 self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Chris Breneman"), 65, 0)
                 self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Eric Varsanyi"), 67, 0)
                 self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Eric Varsanyi"), 67, 0)
                 self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Lubos Medovarsky"), 69, 0)
                 self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Lubos Medovarsky"), 69, 0)
@@ -3336,27 +3328,43 @@ class App(QtCore.QObject):
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('<b>%s</b>' % _("Translator")), 0, 1)
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('<b>%s</b>' % _("Translator")), 0, 1)
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('<b>%s</b>' % _("Corrections")), 0, 2)
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('<b>%s</b>' % _("Corrections")), 0, 2)
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('<b>%s</b>' % _("E-mail")), 0, 3)
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('<b>%s</b>' % _("E-mail")), 0, 3)
+
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "BR - Portuguese"), 1, 0)
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "BR - Portuguese"), 1, 0)
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Carlos Stein"), 1, 1)
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Carlos Stein"), 1, 1)
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "<carlos.stein@gmail.com>"), 1, 3)
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "<carlos.stein@gmail.com>"), 1, 3)
+
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "French"), 2, 0)
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "French"), 2, 0)
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu (Google-Tr)"), 2, 1)
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu (Google-Tr)"), 2, 1)
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % ""), 2, 2)
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % ""), 2, 2)
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 2, 3)
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 2, 3)
-                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "German"), 3, 0)
-                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu (Google-Tr)"), 3, 1)
-                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Jens Karstedt, Detlef Eckardt"), 3, 2)
+
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Hungarian"), 3, 0)
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 3, 1)
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 3, 2)
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 3, 3)
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 3, 3)
-                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Romanian"), 4, 0)
-                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu"), 4, 1)
-                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "<marius_adrian@yahoo.com>"), 4, 3)
-                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Russian"), 5, 0)
-                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Andrey Kultyapov"), 5, 1)
-                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "<camellan@yandex.ru>"), 5, 3)
-                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Spanish"), 6, 0)
-                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu (Google-Tr)"), 6, 1)
-                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % ""), 6, 2)
-                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 6, 3)
+
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Italian"), 4, 0)
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Golfetto Massimiliano"), 4, 1)
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 4, 2)
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "pcb@golfetto.eu"), 4, 3)
+
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "German"), 5, 0)
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu (Google-Tr)"), 5, 1)
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Jens Karstedt, Detlef Eckardt"), 5, 2)
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 5, 3)
+
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Romanian"), 6, 0)
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu"), 6, 1)
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "<marius_adrian@yahoo.com>"), 6, 3)
+
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Russian"), 7, 0)
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Andrey Kultyapov"), 7, 1)
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "<camellan@yandex.ru>"), 7, 3)
+
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Spanish"), 8, 0)
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu (Google-Tr)"), 8, 1)
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % ""), 8, 2)
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 8, 3)
                 self.translator_grid_lay.setColumnStretch(0, 0)
                 self.translator_grid_lay.setColumnStretch(0, 0)
                 self.translators_tab_layout.addStretch()
                 self.translators_tab_layout.addStretch()
 
 
@@ -4120,9 +4128,11 @@ class App(QtCore.QObject):
         obj.multigeo = True
         obj.multigeo = True
         for tooluid, dict_value in obj.tools.items():
         for tooluid, dict_value in obj.tools.items():
             dict_value['solid_geometry'] = deepcopy(obj.solid_geometry)
             dict_value['solid_geometry'] = deepcopy(obj.solid_geometry)
+
         if not isinstance(obj.solid_geometry, list):
         if not isinstance(obj.solid_geometry, list):
             obj.solid_geometry = [obj.solid_geometry]
             obj.solid_geometry = [obj.solid_geometry]
-        obj.solid_geometry[:] = []
+
+        # obj.solid_geometry[:] = []
         obj.plot()
         obj.plot()
 
 
         self.should_we_save = True
         self.should_we_save = True
@@ -4639,7 +4649,7 @@ class App(QtCore.QObject):
         self.defaults.report_usage("on_toggle_grid()")
         self.defaults.report_usage("on_toggle_grid()")
 
 
         self.ui.grid_snap_btn.trigger()
         self.ui.grid_snap_btn.trigger()
-        self.on_grid_snap_triggered(state=True)
+        self.ui.on_grid_snap_triggered(state=True)
 
 
     def on_toggle_grid_lines(self):
     def on_toggle_grid_lines(self):
         self.defaults.report_usage("on_toggle_grd_lines()")
         self.defaults.report_usage("on_toggle_grd_lines()")
@@ -5033,7 +5043,7 @@ class App(QtCore.QObject):
                 self.paste_tool.on_add_tool_by_key()
                 self.paste_tool.on_add_tool_by_key()
 
 
     # It's meant to delete tools in tool tables via a 'Delete' shortcut key but only if certain conditions are met
     # It's meant to delete tools in tool tables via a 'Delete' shortcut key but only if certain conditions are met
-    # See description bellow.
+    # See description below.
     def on_delete_keypress(self):
     def on_delete_keypress(self):
         notebook_widget_name = self.ui.notebook.currentWidget().objectName()
         notebook_widget_name = self.ui.notebook.currentWidget().objectName()
 
 
@@ -6949,7 +6959,6 @@ class App(QtCore.QObject):
                 else:
                 else:
 
 
                     key_modifier = QtWidgets.QApplication.keyboardModifiers()
                     key_modifier = QtWidgets.QApplication.keyboardModifiers()
-
                     if key_modifier == QtCore.Qt.ShiftModifier:
                     if key_modifier == QtCore.Qt.ShiftModifier:
                         mod_key = 'Shift'
                         mod_key = 'Shift'
                     elif key_modifier == QtCore.Qt.ControlModifier:
                     elif key_modifier == QtCore.Qt.ControlModifier:
@@ -6958,20 +6967,19 @@ class App(QtCore.QObject):
                         mod_key = None
                         mod_key = None
 
 
                     try:
                     try:
-                        if mod_key == self.defaults["global_mselect_key"]:
+                        if self.command_active is None:
                             # If the CTRL key is pressed when the LMB is clicked then if the object is selected it will
                             # If the CTRL key is pressed when the LMB is clicked then if the object is selected it will
                             # deselect, and if it's not selected then it will be selected
                             # deselect, and if it's not selected then it will be selected
                             # If there is no active command (self.command_active is None) then we check if we clicked
                             # If there is no active command (self.command_active is None) then we check if we clicked
                             # on a object by checking the bounding limits against mouse click position
                             # on a object by checking the bounding limits against mouse click position
-                            if self.command_active is None:
+                            if mod_key == self.defaults["global_mselect_key"]:
                                 self.select_objects(key='multisel')
                                 self.select_objects(key='multisel')
-                                self.delete_hover_shape()
-                        else:
-                            # If there is no active command (self.command_active is None) then we check if we clicked
-                            # on a object by checking the bounding limits against mouse click position
-                            if self.command_active is None:
+                            else:
+                                # If there is no active command (self.command_active is None) then we check if
+                                # we clicked on a object by checking the bounding limits against mouse click position
                                 self.select_objects()
                                 self.select_objects()
-                                self.delete_hover_shape()
+
+                            self.delete_hover_shape()
                     except Exception as e:
                     except Exception as e:
                         log.warning("FlatCAMApp.on_mouse_click_release_over_plot() select click --> Error: %s" % str(e))
                         log.warning("FlatCAMApp.on_mouse_click_release_over_plot() select click --> Error: %s" % str(e))
                         return
                         return
@@ -8167,7 +8175,6 @@ class App(QtCore.QObject):
     # ###############################################################################################################
     # ###############################################################################################################
     # ### The following section has the functions that are displayed and call the Editor tab CNCJob Tab #############
     # ### The following section has the functions that are displayed and call the Editor tab CNCJob Tab #############
     # ###############################################################################################################
     # ###############################################################################################################
-
     def init_code_editor(self, name):
     def init_code_editor(self, name):
 
 
         self.text_editor_tab = TextEditor(app=self, plain_text=True)
         self.text_editor_tab = TextEditor(app=self, plain_text=True)
@@ -8339,14 +8346,14 @@ class App(QtCore.QObject):
             # set cursor of the code editor with the cursor at the searcehd line
             # set cursor of the code editor with the cursor at the searcehd line
             self.ui.plot_tab_area.currentWidget().code_editor.setTextCursor(cursor)
             self.ui.plot_tab_area.currentWidget().code_editor.setTextCursor(cursor)
 
 
-    def on_filenewscript(self, silent=False, name=None, text=None):
+    def on_filenewscript(self, silent=False):
         """
         """
         Will create a new script file and open it in the Code Editor
         Will create a new script file and open it in the Code Editor
 
 
-        :param silent: if True will not display status messages
-        :param name: if specified will be the name of the new script
-        :param text: pass a source file to the newly created script to be loaded in it
-        :return: None
+        :param silent:  if True will not display status messages
+        :param name:    if specified will be the name of the new script
+        :param text:    pass a source file to the newly created script to be loaded in it
+        :return:        None
         """
         """
         if silent is False:
         if silent is False:
             self.inform.emit('[success] %s' % _("New TCL script file created in Code Editor."))
             self.inform.emit('[success] %s' % _("New TCL script file created in Code Editor."))
@@ -8355,10 +8362,7 @@ class App(QtCore.QObject):
         self.ui.position_label.setText("")
         self.ui.position_label.setText("")
         self.ui.rel_position_label.setText("")
         self.ui.rel_position_label.setText("")
 
 
-        if name is not None:
-            self.new_script_object(name=name, text=text)
-        else:
-            self.new_script_object(text=text)
+        self.new_script_object()
 
 
         # script_text = script_obj.source_file
         # script_text = script_obj.source_file
         #
         #
@@ -8372,9 +8376,9 @@ class App(QtCore.QObject):
         """
         """
         Will open a Tcl script file into the Code Editor
         Will open a Tcl script file into the Code Editor
 
 
-        :param silent: if True will not display status messages
-        :param name: name of a Tcl script file to open
-        :return:
+        :param silent:  if True will not display status messages
+        :param name:    name of a Tcl script file to open
+        :return:        None
         """
         """
 
 
         self.defaults.report_usage("on_fileopenscript")
         self.defaults.report_usage("on_fileopenscript")
@@ -9609,27 +9613,46 @@ class App(QtCore.QObject):
         :param silent:      If True there will be no messages printed to StatusBar
         :param silent:      If True there will be no messages printed to StatusBar
         :return:            None
         :return:            None
         """
         """
-        App.log.debug("open_script()")
 
 
-        with self.proc_container.new(_("Opening TCL Script...")):
+        def obj_init(script_obj, app_obj):
 
 
-            try:
-                with open(filename, "r") as opened_script:
-                    script_content = opened_script.readlines()
-                    script_content = ''.join(script_content)
+            assert isinstance(script_obj, ScriptObject), \
+                "Expected to initialize a ScriptObject but got %s" % type(script_obj)
 
 
-                    if silent is False:
-                        self.inform.emit('[success] %s' % _("TCL script file opened in Code Editor."))
+            if silent is False:
+                app_obj.inform.emit('[success] %s' % _("TCL script file opened in Code Editor."))
+
+            try:
+                script_obj.parse_file(filename)
+            except IOError:
+                app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open file"), filename))
+                return "fail"
+            except ParseError as err:
+                app_obj.inform.emit('[ERROR_NOTCL] %s: %s. %s' % (_("Failed to parse file"), filename, str(err)))
+                app_obj.log.error(str(err))
+                return "fail"
             except Exception as e:
             except Exception as e:
                 log.debug("App.open_script() -> %s" % str(e))
                 log.debug("App.open_script() -> %s" % str(e))
-                self.inform.emit('[ERROR_NOTCL] %s' % _("Failed to open TCL Script."))
-                return
+                msg = '[ERROR] %s' % _("An internal error has occurred. See shell.\n")
+                msg += traceback.format_exc()
+                app_obj.inform.emit(msg)
+                return "fail"
+
+        App.log.debug("open_script()")
+
+        with self.proc_container.new(_("Opening TCL Script...")):
 
 
             # Object name
             # Object name
             script_name = outname or filename.split('/')[-1].split('\\')[-1]
             script_name = outname or filename.split('/')[-1].split('\\')[-1]
 
 
-            # New object creation and file processing
-            self.on_filenewscript(name=script_name, text=script_content)
+            # Object creation
+            ret_val = self.new_object("script", script_name, obj_init, autoselected=False, plot=False)
+            if ret_val == 'fail':
+                filename = self.defaults['global_tcl_path'] + '/' + script_name
+                ret_val = self.new_object("script", script_name, obj_init, autoselected=False, plot=False)
+                if ret_val == 'fail':
+                    self.inform.emit('[ERROR_NOTCL]%s' % _('Failed to open TCL Script.'))
+                    return 'fail'
 
 
             # Register recent file
             # Register recent file
             self.file_opened.emit("script", filename)
             self.file_opened.emit("script", filename)
@@ -10611,29 +10634,6 @@ class App(QtCore.QObject):
                 update_colors=(new_color, new_line_color)
                 update_colors=(new_color, new_line_color)
             )
             )
 
 
-    def on_grid_snap_triggered(self, state):
-        """
-
-        :param state:   A parameter with the state of the grid, boolean
-
-        :return:
-        """
-        if state:
-            self.ui.snap_infobar_label.setPixmap(QtGui.QPixmap(self.resource_location + '/snap_filled_16.png'))
-        else:
-            self.ui.snap_infobar_label.setPixmap(QtGui.QPixmap(self.resource_location + '/snap_16.png'))
-
-        self.ui.snap_infobar_label.clicked_state = state
-
-    def on_grid_icon_snap_clicked(self):
-        """
-        Slot called by clicking a GUI element, in this case a FCLabel
-
-        :return:
-        """
-        if isinstance(self.sender(), FCLabel):
-            self.ui.grid_snap_btn.trigger()
-
     def generate_cnc_job(self, objects):
     def generate_cnc_job(self, objects):
         """
         """
         Slot that will be called by clicking an entry in the contextual menu generated in the Project Tab tree
         Slot that will be called by clicking an entry in the contextual menu generated in the Project Tab tree
@@ -10836,21 +10836,6 @@ class App(QtCore.QObject):
             #                       no_km)
             #                       no_km)
             # QtWidgets.qApp.sendEvent(self.shell._edit, f)
             # QtWidgets.qApp.sendEvent(self.shell._edit, f)
 
 
-    def on_toggle_shell_from_settings(self, state):
-        """
-        Toggle shell: if is visible close it, if it is closed then open it
-        :return: None
-        """
-
-        self.defaults.report_usage("on_toggle_shell_from_settings()")
-
-        if state is True:
-            if not self.ui.shell_dock.isVisible():
-                self.ui.shell_dock.show()
-        else:
-            if self.ui.shell_dock.isVisible():
-                self.ui.shell_dock.hide()
-
     def shell_message(self, msg, show=False, error=False, warning=False, success=False, selected=False):
     def shell_message(self, msg, show=False, error=False, warning=False, success=False, selected=False):
         """
         """
         Shows a message on the FlatCAM Shell
         Shows a message on the FlatCAM Shell

+ 69 - 0
assets/examples/files/test.gbr

@@ -0,0 +1,69 @@
+G04*
+G04 GERBER (RE)GENERATED BY FLATCAM v8.992 - www.flatcam.org - Version Date: 2020/05/01*
+G04 Filename: test.gbr*
+G04 Created on : Friday, 01 May 2020 at 17:03*
+%INBottom.gbr*%
+%MOMM*%
+%ADD13C,0.8*%
+%ADD14C,1.27*%
+%ADD15C,1.27*%
+%ADD16R,1.5X1.5*%
+%ADD17C,1.5*%
+%FSLAX53Y53*%
+G04*
+G71*
+G90*
+G75*
+G01*
+%LNBottom*%
+%LPD*%
+X20779Y24462D2*
+D13*
+Y27002D1*
+X18229D1*
+X14429D1*
+X18229D2*
+X19509D1*
+Y28272D1*
+X32209Y14302D2*
+X28399D1*
+Y16842D1*
+Y14302D2*
+X18239D1*
+X20779Y16842D2*
+X23329D1*
+Y28272D1*
+X27129D1*
+X20779Y19382D2*
+X14429D1*
+X20779Y21922D2*
+X18239D1*
+X28399D2*
+X32209D1*
+D14*
+X27129Y28272D3*
+D15*
+X19509D3*
+D14*
+X14429Y19382D3*
+D15*
+Y27002D3*
+D14*
+X18239Y21922D3*
+D15*
+Y14302D3*
+D14*
+X32209Y21922D3*
+D15*
+Y14302D3*
+D16*
+X28399Y24462D3*
+D17*
+Y21922D3*
+Y19382D3*
+Y16842D3*
+X20779D3*
+Y19382D3*
+Y21922D3*
+Y24462D3*
+M02*

+ 28 - 0
assets/examples/files/test.txt

@@ -0,0 +1,28 @@
+M48
+;EXCELLON GENERATED BY FLATCAM v8.992 - www.flatcam.org - Version Date: 2020/05/01
+;Filename: test.txt
+;Created on : Friday, 01 May 2020 at 17:04
+INCH,LZ
+;FILE_FORMAT=2:4
+T1F00S00C0.0220
+T2F00S00C0.0354
+%
+T01
+X010681Y011130
+X007681Y011130
+X005681Y010630
+X007181Y008630
+X005681Y007630
+X007181Y005630
+X012681Y005630
+X012681Y008630
+T02
+X011181Y009630
+X011181Y008630
+X011181Y007630
+X011181Y006630
+X008181Y006630
+X008181Y007630
+X008181Y008630
+X008181Y009630
+M30

+ 30 - 0
assets/examples/files/test_1.gbr

@@ -0,0 +1,30 @@
+G04*
+G04 GERBER (RE)GENERATED BY FLATCAM v8.992 - www.flatcam.org - Version Date: 2020/05/01*
+G04 Filename: test_1*
+G04 Created on : Friday, 01 May 2020 at 17:07*
+G04*
+G04 RS-274X GERBER GENERATED BY FLATCAM v8.992 - www.flatcam.org - Version Date: 2020/05/01*
+G04 Filename: test_1_edit*
+G04 Created on : Friday, 01 May 2020 at 17:06*
+%FSLAX24Y24*%
+%MOIN*%
+%ADD10C,0.003937*%
+
+G70*
+G90*
+G01*
+%LPD*%
+D10*
+X05118Y11417D02*
+X05118Y05118D01*
+X05118Y05118D02*
+X08661Y05118D01*
+X08661Y05118D02*
+X08661Y11417D01*
+X08661Y11417D02*
+X05118Y11417D01*
+X08661Y11417D02*
+X05118Y11417D01*
+X08661Y11417D02*
+X05118Y11417D01*
+M02*

+ 19 - 0
assets/examples/open_file.FlatScript

@@ -0,0 +1,19 @@
+# ----------- START: This is needed only for the examples ----------------
+# first set the default location where to search for the files to be open and store it to the ROOT_FOLDER variable 
+set ROOT_FOLDER  [get_sys root_folder_path]
+
+# calculate the resources path for the examples we need to run and store it inside the PATH varaible
+set PATH ${ROOT_FOLDER}/assets/examples/files
+# ----------- END: This is needed only for the examples ----------------
+
+# set the working path to the path that holds the files we are going to work with
+set_path $PATH
+
+# load the GERBER file 
+open_gerber test.gbr
+
+# load the Excellon ifle
+open_excellon test.txt
+
+# plot them all so we can see them on canvas
+plot_all 

BIN
assets/resources/dark_resources/flatcam_icon32_green.png


BIN
assets/resources/dark_resources/snap_16.png


BIN
assets/resources/dark_resources/snap_filled_16.png


BIN
assets/resources/snap_16.png


BIN
assets/resources/snap_filled_16.png


+ 9 - 14
camlib.py

@@ -3472,8 +3472,7 @@ class CNCjob(Geometry):
                     else:
                     else:
                         log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
                         log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
                                   "The loaded Excellon file has no drills ...")
                                   "The loaded Excellon file has no drills ...")
-                        self.app.inform.emit('[ERROR_NOTCL] %s...' %
-                                             _('The loaded Excellon file has no drills'))
+                        self.app.inform.emit('[ERROR_NOTCL] %s...' % _('The loaded Excellon file has no drills'))
                         return 'fail'
                         return 'fail'
                 self.z_cut = deepcopy(old_zcut)
                 self.z_cut = deepcopy(old_zcut)
             log.debug("The total travel distance with Travelling Salesman Algorithm is: %s" % str(measured_distance))
             log.debug("The total travel distance with Travelling Salesman Algorithm is: %s" % str(measured_distance))
@@ -3499,14 +3498,12 @@ class CNCjob(Geometry):
         self.app.inform.emit(_("Finished G-Code generation..."))
         self.app.inform.emit(_("Finished G-Code generation..."))
         return 'OK'
         return 'OK'
 
 
-    def generate_from_multitool_geometry(
-            self, geometry, append=True,
-            tooldia=None, offset=0.0, tolerance=0, z_cut=1.0, z_move=2.0,
-            feedrate=2.0, feedrate_z=2.0, feedrate_rapid=30,
-            spindlespeed=None, spindledir='CW', dwell=False, dwelltime=1.0,
-            multidepth=False, depthpercut=None,
-            toolchange=False, toolchangez=1.0, toolchangexy="0.0, 0.0", extracut=False, extracut_length=0.2,
-            startz=None, endz=2.0, endxy='', pp_geometry_name=None, tool_no=1):
+    def generate_from_multitool_geometry(self, geometry, append=True, tooldia=None, offset=0.0, tolerance=0, z_cut=1.0,
+                                         z_move=2.0, feedrate=2.0, feedrate_z=2.0, feedrate_rapid=30,
+                                         spindlespeed=None, spindledir='CW', dwell=False, dwelltime=1.0,
+                                         multidepth=False, depthpercut=None, toolchange=False, toolchangez=1.0,
+                                         toolchangexy="0.0, 0.0", extracut=False, extracut_length=0.2,
+                                         startz=None, endz=2.0, endxy='', pp_geometry_name=None, tool_no=1):
         """
         """
         Algorithm to generate from multitool Geometry.
         Algorithm to generate from multitool Geometry.
 
 
@@ -5025,8 +5022,7 @@ class CNCjob(Geometry):
 
 
         return path
         return path
 
 
-    def linear2gcode(self, linear, tolerance=0, down=True, up=True,
-                     z_cut=None, z_move=None, zdownrate=None,
+    def linear2gcode(self, linear, tolerance=0, down=True, up=True, z_cut=None, z_move=None, zdownrate=None,
                      feedrate=None, feedrate_z=None, feedrate_rapid=None, cont=False, old_point=(0, 0)):
                      feedrate=None, feedrate_z=None, feedrate_rapid=None, cont=False, old_point=(0, 0)):
         """
         """
 
 
@@ -5119,8 +5115,7 @@ class CNCjob(Geometry):
                 # For Incremental coordinates type G91
                 # For Incremental coordinates type G91
                 # next_x = pt[0] - prev_x
                 # next_x = pt[0] - prev_x
                 # next_y = pt[1] - prev_y
                 # next_y = pt[1] - prev_y
-                self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                     _('G91 coordinates not implemented ...'))
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _('G91 coordinates not implemented ...'))
                 next_x = pt[0]
                 next_x = pt[0]
                 next_y = pt[1]
                 next_y = pt[1]
 
 

+ 1 - 0
defaults.py

@@ -28,6 +28,7 @@ class FlatCAMDefaults:
         "version": 8.992,   # defaults format version, not necessarily equal to app version
         "version": 8.992,   # defaults format version, not necessarily equal to app version
         "first_run": True,
         "first_run": True,
         "units": "MM",
         "units": "MM",
+        "root_folder_path": '',
         "global_serial": 0,
         "global_serial": 0,
         "global_stats": dict(),
         "global_stats": dict(),
         "global_tabs_detachable": True,
         "global_tabs_detachable": True,

+ 3 - 3
flatcamEditors/FlatCAMExcEditor.py

@@ -2837,7 +2837,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         # start with GRID toolbar activated
         # start with GRID toolbar activated
         if self.app.ui.grid_snap_btn.isChecked() is False:
         if self.app.ui.grid_snap_btn.isChecked() is False:
             self.app.ui.grid_snap_btn.trigger()
             self.app.ui.grid_snap_btn.trigger()
-            self.app.on_grid_snap_triggered(state=True)
+            self.app.ui.on_grid_snap_triggered(state=True)
 
 
         self.app.ui.popmenu_disable.setVisible(False)
         self.app.ui.popmenu_disable.setVisible(False)
         self.app.ui.cmenu_newmenu.menuAction().setVisible(False)
         self.app.ui.cmenu_newmenu.menuAction().setVisible(False)
@@ -3226,7 +3226,7 @@ class FlatCAMExcEditor(QtCore.QObject):
             spec = {"C": float(tool_dia[0])}
             spec = {"C": float(tool_dia[0])}
             self.new_tools[name] = spec
             self.new_tools[name] = spec
 
 
-            # add in self.tools the 'solid_geometry' key, the value (a list) is populated bellow
+            # add in self.tools the 'solid_geometry' key, the value (a list) is populated below
             self.new_tools[name]['solid_geometry'] = []
             self.new_tools[name]['solid_geometry'] = []
 
 
             # create the self.drills for the new Excellon object (the one with edited content)
             # create the self.drills for the new Excellon object (the one with edited content)
@@ -3258,7 +3258,7 @@ class FlatCAMExcEditor(QtCore.QObject):
                 spec = {"C": float(tool_dia[0])}
                 spec = {"C": float(tool_dia[0])}
                 self.new_tools[name] = spec
                 self.new_tools[name] = spec
 
 
-                # add in self.tools the 'solid_geometry' key, the value (a list) is populated bellow
+                # add in self.tools the 'solid_geometry' key, the value (a list) is populated below
                 self.new_tools[name]['solid_geometry'] = []
                 self.new_tools[name]['solid_geometry'] = []
 
 
             # create the self.slots for the new Excellon object (the one with edited content)
             # create the self.slots for the new Excellon object (the one with edited content)

+ 1 - 1
flatcamEditors/FlatCAMGeoEditor.py

@@ -4100,7 +4100,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
         # start with GRID toolbar activated
         # start with GRID toolbar activated
         if self.app.ui.grid_snap_btn.isChecked() is False:
         if self.app.ui.grid_snap_btn.isChecked() is False:
             self.app.ui.grid_snap_btn.trigger()
             self.app.ui.grid_snap_btn.trigger()
-            self.app.on_grid_snap_triggered(state=True)
+            self.app.ui.on_grid_snap_triggered(state=True)
 
 
     def on_buffer_tool(self):
     def on_buffer_tool(self):
         buff_tool = BufferSelectionTool(self.app, self)
         buff_tool = BufferSelectionTool(self.app, self)

+ 53 - 5
flatcamEditors/FlatCAMGrbEditor.py

@@ -12,6 +12,8 @@ from shapely.geometry import LineString, LinearRing, MultiLineString, Point, Pol
 from shapely.ops import cascaded_union
 from shapely.ops import cascaded_union
 import shapely.affinity as affinity
 import shapely.affinity as affinity
 
 
+from vispy.geometry import Rect
+
 import threading
 import threading
 import time
 import time
 from copy import copy, deepcopy
 from copy import copy, deepcopy
@@ -3701,7 +3703,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         # start with GRID toolbar activated
         # start with GRID toolbar activated
         if self.app.ui.grid_snap_btn.isChecked() is False:
         if self.app.ui.grid_snap_btn.isChecked() is False:
             self.app.ui.grid_snap_btn.trigger()
             self.app.ui.grid_snap_btn.trigger()
-            self.app.on_grid_snap_triggered(state=True)
+            self.app.ui.on_grid_snap_triggered(state=True)
 
 
         # adjust the visibility of some of the canvas context menu
         # adjust the visibility of some of the canvas context menu
         self.app.ui.popmenu_edit.setVisible(False)
         self.app.ui.popmenu_edit.setVisible(False)
@@ -3721,6 +3723,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
         except Exception as e:
         except Exception as e:
             log.debug("FlatCAMGrbEditor.deactivate_grb_editor() --> %s" % str(e))
             log.debug("FlatCAMGrbEditor.deactivate_grb_editor() --> %s" % str(e))
 
 
+        self.clear()
+
         # adjust the status of the menu entries related to the editor
         # adjust the status of the menu entries related to the editor
         self.app.ui.menueditedit.setDisabled(False)
         self.app.ui.menueditedit.setDisabled(False)
         self.app.ui.menueditok.setDisabled(True)
         self.app.ui.menueditok.setDisabled(True)
@@ -3729,7 +3733,6 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.app.ui.popmenu_save.setVisible(False)
         self.app.ui.popmenu_save.setVisible(False)
 
 
         self.disconnect_canvas_event_handlers()
         self.disconnect_canvas_event_handlers()
-        self.clear()
         self.app.ui.grb_edit_toolbar.setDisabled(True)
         self.app.ui.grb_edit_toolbar.setDisabled(True)
 
 
         settings = QSettings("Open Source", "FlatCAM")
         settings = QSettings("Open Source", "FlatCAM")
@@ -3937,8 +3940,12 @@ class FlatCAMGrbEditor(QtCore.QObject):
             pass
             pass
 
 
     def clear(self):
     def clear(self):
+        self.thread.quit()
+
         self.active_tool = None
         self.active_tool = None
         self.selected = []
         self.selected = []
+        self.storage_dict.clear()
+        self.results.clear()
 
 
         self.shapes.clear(update=True)
         self.shapes.clear(update=True)
         self.tool_shape.clear(update=True)
         self.tool_shape.clear(update=True)
@@ -3968,7 +3975,16 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
 
         file_units = self.gerber_obj.units if self.gerber_obj.units else 'IN'
         file_units = self.gerber_obj.units if self.gerber_obj.units else 'IN'
         app_units = self.app.defaults['units']
         app_units = self.app.defaults['units']
-        self.conversion_factor = 25.4 if file_units == 'IN' else (1 / 25.4) if file_units != app_units else 1
+        # self.conversion_factor = 25.4 if file_units == 'IN' else (1 / 25.4) if file_units != app_units else 1
+
+        if file_units == app_units:
+            self.conversion_factor = 1
+        else:
+            if file_units == 'IN':
+                self.conversion_factor = 25.4
+            else:
+                self.conversion_factor = 0.0393700787401575
+
 
 
         # Hide original geometry
         # Hide original geometry
         orig_grb_obj.visible = False
         orig_grb_obj.visible = False
@@ -4228,8 +4244,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         else:
         else:
             new_grb_name = self.edited_obj_name + "_edit"
             new_grb_name = self.edited_obj_name + "_edit"
 
 
-        self.app.worker_task.emit({'fcn': self.new_edited_gerber,
-                                   'params': [new_grb_name, self.storage_dict]})
+        self.app.worker_task.emit({'fcn': self.new_edited_gerber, 'params': [new_grb_name, self.storage_dict]})
 
 
     @staticmethod
     @staticmethod
     def update_options(obj):
     def update_options(obj):
@@ -4927,6 +4942,39 @@ class FlatCAMGrbEditor(QtCore.QObject):
     #     self.app.app_cursor.enabled = False
     #     self.app.app_cursor.enabled = False
     #     self.app.app_cursor.enabled = True
     #     self.app.app_cursor.enabled = True
 
 
+    def on_zoom_fit(self):
+        """
+        Callback for zoom-fit request in Gerber Editor
+
+        :return:        None
+        """
+        log.debug("FlatCAMGrbEditor.on_zoom_fit()")
+
+        # calculate all the geometry in the edited Gerber object
+        edit_geo = []
+        for ap_code in self.storage_dict:
+            for geo_el in self.storage_dict[ap_code]['geometry']:
+                actual_geo = geo_el.geo
+                if 'solid' in actual_geo:
+                    edit_geo.append(actual_geo['solid'])
+
+        all_geo = cascaded_union(edit_geo)
+
+        # calculate the bounds values for the edited Gerber object
+        xmin, ymin, xmax, ymax = all_geo.bounds
+
+        if self.app.is_legacy is False:
+            new_rect = Rect(xmin, ymin, xmax, ymax)
+            self.app.plotcanvas.fit_view(rect=new_rect)
+        else:
+            width = xmax - xmin
+            height = ymax - ymin
+            xmin -= 0.05 * width
+            xmax += 0.05 * width
+            ymin -= 0.05 * height
+            ymax += 0.05 * height
+            self.app.plotcanvas.adjust_axes(xmin, ymin, xmax, ymax)
+
     def get_selected(self):
     def get_selected(self):
         """
         """
         Returns list of shapes that are selected in the editor.
         Returns list of shapes that are selected in the editor.

+ 2 - 2
flatcamEditors/FlatCAMTextEditor.py

@@ -25,8 +25,8 @@ if '_' not in builtins.__dict__:
 
 
 class TextEditor(QtWidgets.QWidget):
 class TextEditor(QtWidgets.QWidget):
 
 
-    def __init__(self, app, text=None, plain_text=None):
-        super().__init__()
+    def __init__(self, app, text=None, plain_text=None, parent=None):
+        super().__init__(parent=parent)
 
 
         self.app = app
         self.app = app
         self.plain_text = plain_text
         self.plain_text = plain_text

+ 30 - 5
flatcamGUI/FlatCAMGUI.py

@@ -2366,7 +2366,6 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         # ########################################################################
         # ########################################################################
         # ######################## BUILD PREFERENCES #############################
         # ######################## BUILD PREFERENCES #############################
         # ########################################################################
         # ########################################################################
-
         self.general_defaults_form = GeneralPreferencesUI(decimals=self.decimals)
         self.general_defaults_form = GeneralPreferencesUI(decimals=self.decimals)
         self.gerber_defaults_form = GerberPreferencesUI(decimals=self.decimals)
         self.gerber_defaults_form = GerberPreferencesUI(decimals=self.decimals)
         self.excellon_defaults_form = ExcellonPreferencesUI(decimals=self.decimals)
         self.excellon_defaults_form = ExcellonPreferencesUI(decimals=self.decimals)
@@ -2381,7 +2380,6 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         # ########################################################################
         # ########################################################################
         # ################## RESTORE THE TOOLBAR STATE from file #################
         # ################## RESTORE THE TOOLBAR STATE from file #################
         # ########################################################################
         # ########################################################################
-
         flat_settings = QSettings("Open Source", "FlatCAM")
         flat_settings = QSettings("Open Source", "FlatCAM")
         if flat_settings.contains("saved_gui_state"):
         if flat_settings.contains("saved_gui_state"):
             saved_gui_state = flat_settings.value('saved_gui_state')
             saved_gui_state = flat_settings.value('saved_gui_state')
@@ -2439,15 +2437,42 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
             del qsettings
             del qsettings
 
 
         self.lock_toolbar(lock=lock_state)
         self.lock_toolbar(lock=lock_state)
+        self.on_grid_snap_triggered(state=True)
+
         self.lock_action.triggered[bool].connect(self.lock_toolbar)
         self.lock_action.triggered[bool].connect(self.lock_toolbar)
 
 
         self.pref_open_button.clicked.connect(self.on_preferences_open_folder)
         self.pref_open_button.clicked.connect(self.on_preferences_open_folder)
         self.clear_btn.clicked.connect(self.on_gui_clear)
         self.clear_btn.clicked.connect(self.on_gui_clear)
+        self.grid_snap_btn.triggered.connect(self.on_grid_snap_triggered)
+        self.snap_infobar_label.clicked.connect(self.on_grid_icon_snap_clicked)
 
 
         # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
         # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
         # %%%%%%%%%%%%%%%%% GUI Building FINISHED %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
         # %%%%%%%%%%%%%%%%% GUI Building FINISHED %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
         # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
         # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
 
+    def on_grid_snap_triggered(self, state):
+        """
+
+        :param state:   A parameter with the state of the grid, boolean
+
+        :return:
+        """
+        if state:
+            self.snap_infobar_label.setPixmap(QtGui.QPixmap(self.app.resource_location + '/snap_filled_16.png'))
+        else:
+            self.snap_infobar_label.setPixmap(QtGui.QPixmap(self.app.resource_location + '/snap_16.png'))
+
+        self.snap_infobar_label.clicked_state = state
+
+    def on_grid_icon_snap_clicked(self):
+        """
+        Slot called by clicking a GUI element, in this case a FCLabel
+
+        :return:
+        """
+        if isinstance(self.sender(), FCLabel):
+            self.grid_snap_btn.trigger()
+
     def eventFilter(self, obj, event):
     def eventFilter(self, obj, event):
         """
         """
         Filter the ToolTips display based on a Preferences setting
         Filter the ToolTips display based on a Preferences setting
@@ -3207,7 +3232,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                         else:
                         else:
                             self.app.collection.set_active(names_list[active_index-1])
                             self.app.collection.set_active(names_list[active_index-1])
 
 
-                # Select the object in the Tree bellow the current one
+                # Select the object in the Tree below the current one
                 if key == QtCore.Qt.Key_Down:
                 if key == QtCore.Qt.Key_Down:
                     # make sure it works only for the Project Tab who is an instance of KeySensitiveListView
                     # make sure it works only for the Project Tab who is an instance of KeySensitiveListView
                     focused_wdg = QtWidgets.QApplication.focusWidget()
                     focused_wdg = QtWidgets.QApplication.focusWidget()
@@ -3811,10 +3836,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                         self.app.grb_editor.select_tool('track')
                         self.app.grb_editor.select_tool('track')
                         return
                         return
 
 
-                    # Zoom Fit
+                    # Zoom fit
                     if key == QtCore.Qt.Key_V or key == 'V':
                     if key == QtCore.Qt.Key_V or key == 'V':
                         self.app.grb_editor.launched_from_shortcuts = True
                         self.app.grb_editor.launched_from_shortcuts = True
-                        self.app.on_zoom_fit(None)
+                        self.app.grb_editor.on_zoom_fit()
                         return
                         return
 
 
                 # Show Shortcut list
                 # Show Shortcut list

+ 1 - 1
flatcamGUI/preferences/PreferencesUIManager.py

@@ -45,7 +45,7 @@ class PreferencesUIManager:
         # if Preferences are changed in the Edit -> Preferences tab the value will be set to True
         # if Preferences are changed in the Edit -> Preferences tab the value will be set to True
         self.preferences_changed_flag = False
         self.preferences_changed_flag = False
 
 
-        # when adding entries here read the comments in the  method found bellow named:
+        # when adding entries here read the comments in the  method found below named:
         # def new_object(self, kind, name, initialize, active=True, fit=True, plot=True)
         # def new_object(self, kind, name, initialize, active=True, fit=True, plot=True)
         self.defaults_form_fields = {
         self.defaults_form_fields = {
             # General App
             # General App

+ 16 - 1
flatcamGUI/preferences/general/GeneralAppPrefGroupUI.py

@@ -374,10 +374,25 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
         self.splash_cb.stateChanged.connect(self.on_splash_changed)
         self.splash_cb.stateChanged.connect(self.on_splash_changed)
 
 
         # Monitor the checkbox from the Application Defaults Tab and show the TCL shell or not depending on it's value
         # Monitor the checkbox from the Application Defaults Tab and show the TCL shell or not depending on it's value
-        self.shell_startup_cb.clicked.connect(self.app.on_toggle_shell_from_settings)
+        self.shell_startup_cb.clicked.connect(self.on_toggle_shell_from_settings)
 
 
         self.language_apply_btn.clicked.connect(lambda: fcTranslate.on_language_apply_click(app=self.app, restart=True))
         self.language_apply_btn.clicked.connect(lambda: fcTranslate.on_language_apply_click(app=self.app, restart=True))
 
 
+    def on_toggle_shell_from_settings(self, state):
+        """
+        Toggle shell: if is visible close it, if it is closed then open it
+        :return: None
+        """
+
+        self.app.defaults.report_usage("on_toggle_shell_from_settings()")
+
+        if state is True:
+            if not self.app.ui.shell_dock.isVisible():
+                self.app.ui.shell_dock.show()
+        else:
+            if self.app.ui.shell_dock.isVisible():
+                self.app.ui.shell_dock.hide()
+
     @staticmethod
     @staticmethod
     def on_splash_changed(state):
     def on_splash_changed(state):
         qsettings = QSettings("Open Source", "FlatCAM")
         qsettings = QSettings("Open Source", "FlatCAM")

+ 1 - 1
flatcamGUI/preferences/general/GeneralGUIPrefGroupUI.py

@@ -377,7 +377,7 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI2):
         self.app.connect_toolbar_signals()
         self.app.connect_toolbar_signals()
 
 
         self.app.ui.grid_snap_btn.setChecked(True)
         self.app.ui.grid_snap_btn.setChecked(True)
-        self.app.on_grid_snap_triggered(state=True)
+        self.app.ui.on_grid_snap_triggered(state=True)
 
 
         self.app.ui.grid_gap_x_entry.setText(str(self.app.defaults["global_gridx"]))
         self.app.ui.grid_gap_x_entry.setText(str(self.app.defaults["global_gridx"]))
         self.app.ui.grid_gap_y_entry.setText(str(self.app.defaults["global_gridy"]))
         self.app.ui.grid_gap_y_entry.setText(str(self.app.defaults["global_gridy"]))

+ 1 - 1
flatcamObjects/FlatCAMCNCJob.py

@@ -364,7 +364,7 @@ class CNCJobObject(FlatCAMObj, CNCjob):
         self.units_found = self.app.defaults['units']
         self.units_found = self.app.defaults['units']
 
 
         # this signal has to be connected to it's slot before the defaults are populated
         # this signal has to be connected to it's slot before the defaults are populated
-        # the decision done in the slot has to override the default value set bellow
+        # the decision done in the slot has to override the default value set below
         self.ui.toolchange_cb.toggled.connect(self.on_toolchange_custom_clicked)
         self.ui.toolchange_cb.toggled.connect(self.on_toolchange_custom_clicked)
 
 
         self.form_fields.update({
         self.form_fields.update({

+ 26 - 0
flatcamObjects/FlatCAMGeometry.py

@@ -1114,6 +1114,26 @@ class GeometryObject(FlatCAMObj, Geometry):
             self.ui.tipanglelabel.show()
             self.ui.tipanglelabel.show()
             self.ui.tipangle_entry.show()
             self.ui.tipangle_entry.show()
             self.ui.cutz_entry.setDisabled(True)
             self.ui.cutz_entry.setDisabled(True)
+            self.ui.cutzlabel.setToolTip(
+                _("Disabled because the tool is V-shape.\n"
+                  "For V-shape tools the depth of cut is\n"
+                  "calculated from other parameters like:\n"
+                  "- 'V-tip Angle' -> angle at the tip of the tool\n"
+                  "- 'V-tip Dia' -> diameter at the tip of the tool \n"
+                  "- Tool Dia -> 'Dia' column found in the Tool Table\n"
+                  "NB: a value of zero means that Tool Dia = 'V-tip Dia'"
+                )
+            )
+            self.ui.cutz_entry.setToolTip(
+                _("Disabled because the tool is V-shape.\n"
+                  "For V-shape tools the depth of cut is\n"
+                  "calculated from other parameters like:\n"
+                  "- 'V-tip Angle' -> angle at the tip of the tool\n"
+                  "- 'V-tip Dia' -> diameter at the tip of the tool \n"
+                  "- Tool Dia -> 'Dia' column found in the Tool Table\n"
+                  "NB: a value of zero means that Tool Dia = 'V-tip Dia'"
+                  )
+            )
 
 
             self.update_cutz()
             self.update_cutz()
         else:
         else:
@@ -1122,6 +1142,12 @@ class GeometryObject(FlatCAMObj, Geometry):
             self.ui.tipanglelabel.hide()
             self.ui.tipanglelabel.hide()
             self.ui.tipangle_entry.hide()
             self.ui.tipangle_entry.hide()
             self.ui.cutz_entry.setDisabled(False)
             self.ui.cutz_entry.setDisabled(False)
+            self.ui.cutzlabel.setToolTip(
+                _("Cutting depth (negative)\n"
+                  "below the copper surface."
+                )
+            )
+            self.ui.cutz_entry.setToolTip('')
 
 
     def update_cutz(self):
     def update_cutz(self):
         vdia = float(self.ui.tipdia_entry.get_value())
         vdia = float(self.ui.tipdia_entry.get_value())

+ 33 - 6
flatcamObjects/FlatCAMScript.py

@@ -50,15 +50,14 @@ class ScriptObject(FlatCAMObj):
 
 
         self.units = ''
         self.units = ''
 
 
+        self.script_editor_tab = None
+
         self.ser_attrs = ['options', 'kind', 'source_file']
         self.ser_attrs = ['options', 'kind', 'source_file']
         self.source_file = ''
         self.source_file = ''
         self.script_code = ''
         self.script_code = ''
 
 
         self.units_found = self.app.defaults['units']
         self.units_found = self.app.defaults['units']
 
 
-        # self.script_editor_tab = TextEditor(app=self.app, plain_text=True)
-        self.script_editor_tab = TextEditor(app=self.app, plain_text=True)
-
     def set_ui(self, ui):
     def set_ui(self, ui):
         """
         """
         Sets the Object UI in Selected Tab for the FlatCAM Script type of object.
         Sets the Object UI in Selected Tab for the FlatCAM Script type of object.
@@ -87,6 +86,8 @@ class ScriptObject(FlatCAMObj):
                 '<span style="color:red;"><b>Advanced</b></span>'
                 '<span style="color:red;"><b>Advanced</b></span>'
             ))
             ))
 
 
+        self.script_editor_tab = TextEditor(app=self.app, plain_text=True, parent=self.app.ui)
+
         # tab_here = False
         # tab_here = False
         # # try to not add too many times a tab that it is already installed
         # # try to not add too many times a tab that it is already installed
         # for idx in range(self.app.ui.plot_tab_area.count()):
         # for idx in range(self.app.ui.plot_tab_area.count()):
@@ -99,8 +100,8 @@ class ScriptObject(FlatCAMObj):
         #     self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor"))
         #     self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor"))
         #     self.script_editor_tab.setObjectName(self.options['name'])
         #     self.script_editor_tab.setObjectName(self.options['name'])
 
 
-        self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor"))
-        self.script_editor_tab.setObjectName(self.options['name'])
+        # self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor"))
+        # self.script_editor_tab.setObjectName(self.options['name'])
 
 
         # first clear previous text in text editor (if any)
         # first clear previous text in text editor (if any)
         # self.script_editor_tab.code_editor.clear()
         # self.script_editor_tab.code_editor.clear()
@@ -111,7 +112,7 @@ class ScriptObject(FlatCAMObj):
 
 
         self.script_editor_tab.buttonRun.show()
         self.script_editor_tab.buttonRun.show()
 
 
-        # Switch plot_area to CNCJob tab
+        # Switch plot_area to Script Editor tab
         self.app.ui.plot_tab_area.setCurrentWidget(self.script_editor_tab)
         self.app.ui.plot_tab_area.setCurrentWidget(self.script_editor_tab)
 
 
         flt = "FlatCAM Scripts (*.FlatScript);;All Files (*.*)"
         flt = "FlatCAM Scripts (*.FlatScript);;All Files (*.*)"
@@ -150,6 +151,32 @@ class ScriptObject(FlatCAMObj):
     def build_ui(self):
     def build_ui(self):
         FlatCAMObj.build_ui(self)
         FlatCAMObj.build_ui(self)
 
 
+        tab_here = False
+        # try to not add too many times a tab that it is already installed
+        for idx in range(self.app.ui.plot_tab_area.count()):
+            if self.app.ui.plot_tab_area.widget(idx).objectName() == self.options['name']:
+                tab_here = True
+                break
+
+        # add the tab if it is not already added
+        if tab_here is False:
+            self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor"))
+            self.script_editor_tab.setObjectName(self.options['name'])
+            self.app.ui.plot_tab_area.setCurrentWidget(self.script_editor_tab)
+
+    def parse_file(self, filename):
+        """
+        Will set an attribute of the object, self.source_file, with the parsed data.
+
+        :param filename:    Tcl Script file to parse
+        :return:            None
+        """
+        with open(filename, "r") as opened_script:
+            script_content = opened_script.readlines()
+            script_content = ''.join(script_content)
+
+        self.source_file = script_content
+
     def handle_run_code(self):
     def handle_run_code(self):
         # trying to run a Tcl command without having the Shell open will create some warnings because the Tcl Shell
         # trying to run a Tcl command without having the Shell open will create some warnings because the Tcl Shell
         # tries to print on a hidden widget, therefore show the dock if hidden
         # tries to print on a hidden widget, therefore show the dock if hidden

+ 1 - 1
flatcamParsers/ParseExcellon.py

@@ -426,7 +426,7 @@ class Excellon(Geometry):
                                     # it's possible that tool definition has only tool number and no diameter info
                                     # it's possible that tool definition has only tool number and no diameter info
                                     # (those could be in another file like PCB Wizard do)
                                     # (those could be in another file like PCB Wizard do)
                                     # then match.group(2) = None and float(None) will create the exception
                                     # then match.group(2) = None and float(None) will create the exception
-                                    # the bellow construction is so each tool will have a slightly different diameter
+                                    # the below construction is so each tool will have a slightly different diameter
                                     # starting with a default value, to allow Excellon editing after that
                                     # starting with a default value, to allow Excellon editing after that
                                     self.diameterless = True
                                     self.diameterless = True
                                     self.app.inform.emit('[WARNING] %s%s %s' %
                                     self.app.inform.emit('[WARNING] %s%s %s' %

+ 106 - 9
flatcamTools/ToolCutOut.py

@@ -293,12 +293,12 @@ class CutOut(FlatCAMTool):
                             font-weight: bold;
                             font-weight: bold;
                         }
                         }
                         """)
                         """)
-        self.layout.addWidget(self.rect_cutout_object_btn)
+        grid0.addWidget(self.rect_cutout_object_btn, 21, 0, 1, 2)
 
 
         separator_line = QtWidgets.QFrame()
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid0.addWidget(separator_line, 21, 0, 1, 2)
+        grid0.addWidget(separator_line, 22, 0, 1, 2)
 
 
         # Title5
         # Title5
         title_manual_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % _('B. Manual Bridge Gaps'))
         title_manual_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % _('B. Manual Bridge Gaps'))
@@ -307,7 +307,7 @@ class CutOut(FlatCAMTool):
               "This is done by mouse clicking on the perimeter of the\n"
               "This is done by mouse clicking on the perimeter of the\n"
               "Geometry object that is used as a cutout object. ")
               "Geometry object that is used as a cutout object. ")
         )
         )
-        grid0.addWidget(title_manual_label, 22, 0, 1, 2)
+        grid0.addWidget(title_manual_label, 23, 0, 1, 2)
 
 
         # Manual Geo Object
         # Manual Geo Object
         self.man_object_combo = FCComboBox()
         self.man_object_combo = FCComboBox()
@@ -322,8 +322,8 @@ class CutOut(FlatCAMTool):
         )
         )
         # self.man_object_label.setMinimumWidth(60)
         # self.man_object_label.setMinimumWidth(60)
 
 
-        grid0.addWidget(self.man_object_label, 23, 0, 1, 2)
-        grid0.addWidget(self.man_object_combo, 24, 0, 1, 2)
+        grid0.addWidget(self.man_object_label, 25, 0, 1, 2)
+        grid0.addWidget(self.man_object_combo, 26, 0, 1, 2)
 
 
         self.man_geo_creation_btn = FCButton(_("Generate Manual Geometry"))
         self.man_geo_creation_btn = FCButton(_("Generate Manual Geometry"))
         self.man_geo_creation_btn.setToolTip(
         self.man_geo_creation_btn.setToolTip(
@@ -338,7 +338,7 @@ class CutOut(FlatCAMTool):
                             font-weight: bold;
                             font-weight: bold;
                         }
                         }
                         """)
                         """)
-        grid0.addWidget(self.man_geo_creation_btn, 25, 0, 1, 2)
+        grid0.addWidget(self.man_geo_creation_btn, 28, 0, 1, 2)
 
 
         self.man_gaps_creation_btn = FCButton(_("Manual Add Bridge Gaps"))
         self.man_gaps_creation_btn = FCButton(_("Manual Add Bridge Gaps"))
         self.man_gaps_creation_btn.setToolTip(
         self.man_gaps_creation_btn.setToolTip(
@@ -354,7 +354,7 @@ class CutOut(FlatCAMTool):
                             font-weight: bold;
                             font-weight: bold;
                         }
                         }
                         """)
                         """)
-        grid0.addWidget(self.man_gaps_creation_btn, 27, 0, 1, 2)
+        grid0.addWidget(self.man_gaps_creation_btn, 30, 0, 1, 2)
 
 
         self.layout.addStretch()
         self.layout.addStretch()
 
 
@@ -394,6 +394,9 @@ class CutOut(FlatCAMTool):
         self.x_pos = None
         self.x_pos = None
         self.y_pos = None
         self.y_pos = None
 
 
+        # store the default data for the resulting Geometry Object
+        self.default_data = {}
+
         # 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)
@@ -454,6 +457,48 @@ class CutOut(FlatCAMTool):
         self.convex_box.set_value(self.app.defaults['tools_cutout_convexshape'])
         self.convex_box.set_value(self.app.defaults['tools_cutout_convexshape'])
         self.type_obj_radio.set_value('grb')
         self.type_obj_radio.set_value('grb')
 
 
+        self.default_data.update({
+            "plot":             True,
+            "cutz":             float(self.app.defaults["geometry_cutz"]),
+            "multidepth":       self.app.defaults["geometry_multidepth"],
+            "depthperpass":     float(self.app.defaults["geometry_depthperpass"]),
+            "vtipdia":          float(self.app.defaults["geometry_vtipdia"]),
+            "vtipangle":        float(self.app.defaults["geometry_vtipangle"]),
+            "travelz":          float(self.app.defaults["geometry_travelz"]),
+            "feedrate":         float(self.app.defaults["geometry_feedrate"]),
+            "feedrate_z":       float(self.app.defaults["geometry_feedrate_z"]),
+            "feedrate_rapid":   float(self.app.defaults["geometry_feedrate_rapid"]),
+            "spindlespeed":     self.app.defaults["geometry_spindlespeed"],
+            "dwell":            self.app.defaults["geometry_dwell"],
+            "dwelltime":        float(self.app.defaults["geometry_dwelltime"]),
+            "ppname_g":         self.app.defaults["geometry_ppname_g"],
+            "extracut":         self.app.defaults["geometry_extracut"],
+            "extracut_length":  float(self.app.defaults["geometry_extracut_length"]),
+            "toolchange":       self.app.defaults["geometry_toolchange"],
+            "toolchangexy":     self.app.defaults["geometry_toolchangexy"],
+            "toolchangez":      float(self.app.defaults["geometry_toolchangez"]),
+            "startz":           self.app.defaults["geometry_startz"],
+            "endz":             float(self.app.defaults["geometry_endz"]),
+
+            # NCC
+            "tools_nccoperation":       self.app.defaults["tools_nccoperation"],
+            "tools_nccmilling_type":    self.app.defaults["tools_nccmilling_type"],
+            "tools_nccoverlap":         float(self.app.defaults["tools_nccoverlap"]),
+            "tools_nccmargin":          float(self.app.defaults["tools_nccmargin"]),
+            "tools_nccmethod":          self.app.defaults["tools_nccmethod"],
+            "tools_nccconnect":         self.app.defaults["tools_nccconnect"],
+            "tools_ncccontour":         self.app.defaults["tools_ncccontour"],
+            "tools_ncc_offset_choice":  self.app.defaults["tools_ncc_offset_choice"],
+            "tools_ncc_offset_value":   float(self.app.defaults["tools_ncc_offset_value"]),
+
+            # Paint
+            "tools_paintoverlap":       float(self.app.defaults["tools_paintoverlap"]),
+            "tools_paintmargin":        float(self.app.defaults["tools_paintmargin"]),
+            "tools_paintmethod":        self.app.defaults["tools_paintmethod"],
+            "tools_pathconnect":        self.app.defaults["tools_pathconnect"],
+            "tools_paintcontour":       self.app.defaults["tools_paintcontour"],
+        })
+
     def on_freeform_cutout(self):
     def on_freeform_cutout(self):
         log.debug("Cutout.on_freeform_cutout() was launched ...")
         log.debug("Cutout.on_freeform_cutout() was launched ...")
 
 
@@ -622,8 +667,8 @@ class CutOut(FlatCAMTool):
 
 
                     solid_geo += cutout_handler(geom=geom_struct)
                     solid_geo += cutout_handler(geom=geom_struct)
 
 
-            geo_obj.solid_geometry = deepcopy(solid_geo)
             xmin, ymin, xmax, ymax = recursive_bounds(geo_obj.solid_geometry)
             xmin, ymin, xmax, ymax = recursive_bounds(geo_obj.solid_geometry)
+            geo_obj.solid_geometry = deepcopy(solid_geo)
             geo_obj.options['xmin'] = xmin
             geo_obj.options['xmin'] = xmin
             geo_obj.options['ymin'] = ymin
             geo_obj.options['ymin'] = ymin
             geo_obj.options['xmax'] = xmax
             geo_obj.options['xmax'] = xmax
@@ -633,6 +678,23 @@ class CutOut(FlatCAMTool):
             geo_obj.options['multidepth'] = self.mpass_cb.get_value()
             geo_obj.options['multidepth'] = self.mpass_cb.get_value()
             geo_obj.options['depthperpass'] = self.maxdepth_entry.get_value()
             geo_obj.options['depthperpass'] = self.maxdepth_entry.get_value()
 
 
+            geo_obj.tools.update({
+                1: {
+                    'tooldia': str(dia),
+                    'offset': 'Path',
+                    'offset_value': 0.0,
+                    'type': _('Rough'),
+                    'tool_type': 'C1',
+                    'data': self.default_data,
+                    'solid_geometry': geo_obj.solid_geometry
+                }
+            })
+            geo_obj.multigeo = True
+            geo_obj.tools[1]['data']['name'] = outname
+            geo_obj.tools[1]['data']['cutz'] = self.cutz_entry.get_value()
+            geo_obj.tools[1]['data']['multidepth'] = self.mpass_cb.get_value()
+            geo_obj.tools[1]['data']['depthperpass'] = self.maxdepth_entry.get_value()
+
         outname = cutout_obj.options["name"] + "_cutout"
         outname = cutout_obj.options["name"] + "_cutout"
         self.app.new_object('geometry', outname, geo_init)
         self.app.new_object('geometry', outname, geo_init)
 
 
@@ -759,6 +821,7 @@ class CutOut(FlatCAMTool):
                 return proc_geometry
                 return proc_geometry
 
 
             if kind == 'single':
             if kind == 'single':
+                # fuse the lines
                 object_geo = unary_union(object_geo)
                 object_geo = unary_union(object_geo)
 
 
                 xmin, ymin, xmax, ymax = object_geo.bounds
                 xmin, ymin, xmax, ymax = object_geo.bounds
@@ -805,11 +868,28 @@ class CutOut(FlatCAMTool):
                                          _("Rectangular cutout with negative margin is not possible."))
                                          _("Rectangular cutout with negative margin is not possible."))
                     return "fail"
                     return "fail"
 
 
-            geo_obj.solid_geometry = deepcopy(solid_geo)
             geo_obj.options['cnctooldia'] = str(dia)
             geo_obj.options['cnctooldia'] = str(dia)
             geo_obj.options['cutz'] = self.cutz_entry.get_value()
             geo_obj.options['cutz'] = self.cutz_entry.get_value()
             geo_obj.options['multidepth'] = self.mpass_cb.get_value()
             geo_obj.options['multidepth'] = self.mpass_cb.get_value()
             geo_obj.options['depthperpass'] = self.maxdepth_entry.get_value()
             geo_obj.options['depthperpass'] = self.maxdepth_entry.get_value()
+            geo_obj.solid_geometry = deepcopy(solid_geo)
+
+            geo_obj.tools.update({
+                1: {
+                    'tooldia': str(dia),
+                    'offset': 'Path',
+                    'offset_value': 0.0,
+                    'type': _('Rough'),
+                    'tool_type': 'C1',
+                    'data': self.default_data,
+                    'solid_geometry': geo_obj.solid_geometry
+                }
+            })
+            geo_obj.multigeo = True
+            geo_obj.tools[1]['data']['name'] = outname
+            geo_obj.tools[1]['data']['cutz'] = self.cutz_entry.get_value()
+            geo_obj.tools[1]['data']['multidepth'] = self.mpass_cb.get_value()
+            geo_obj.tools[1]['data']['depthperpass'] = self.maxdepth_entry.get_value()
 
 
         outname = cutout_obj.options["name"] + "_cutout"
         outname = cutout_obj.options["name"] + "_cutout"
         ret = self.app.new_object('geometry', outname, geo_init)
         ret = self.app.new_object('geometry', outname, geo_init)
@@ -954,6 +1034,23 @@ class CutOut(FlatCAMTool):
             geo_obj.options['multidepth'] = self.mpass_cb.get_value()
             geo_obj.options['multidepth'] = self.mpass_cb.get_value()
             geo_obj.options['depthperpass'] = self.maxdepth_entry.get_value()
             geo_obj.options['depthperpass'] = self.maxdepth_entry.get_value()
 
 
+            geo_obj.tools.update({
+                1: {
+                    'tooldia': str(dia),
+                    'offset': 'Path',
+                    'offset_value': 0.0,
+                    'type': _('Rough'),
+                    'tool_type': 'C1',
+                    'data': self.default_data,
+                    'solid_geometry': geo_obj.solid_geometry
+                }
+            })
+            geo_obj.multigeo = True
+            geo_obj.tools[1]['data']['name'] = outname
+            geo_obj.tools[1]['data']['cutz'] = self.cutz_entry.get_value()
+            geo_obj.tools[1]['data']['multidepth'] = self.mpass_cb.get_value()
+            geo_obj.tools[1]['data']['depthperpass'] = self.maxdepth_entry.get_value()
+
         outname = cutout_obj.options["name"] + "_cutout"
         outname = cutout_obj.options["name"] + "_cutout"
         self.app.new_object('geometry', outname, geo_init)
         self.app.new_object('geometry', outname, geo_init)
 
 

+ 1 - 1
flatcamTools/ToolFilm.py

@@ -128,7 +128,7 @@ class Film(FlatCAMTool):
 
 
         self.tf_box_combo_label = QtWidgets.QLabel('%s:' % _("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 as container for the\n "
               "selected object for which we create the film.\n"
               "selected object for which we create the film.\n"
               "Usually it is the PCB outline but it can be also the\n"
               "Usually it is the PCB outline but it can be also the\n"
               "same object for which the film is created.")
               "same object for which the film is created.")

+ 78 - 79
flatcamTools/ToolPanelize.py

@@ -14,6 +14,8 @@ from copy import deepcopy
 import numpy as np
 import numpy as np
 
 
 import shapely.affinity as affinity
 import shapely.affinity as affinity
+from shapely.ops import unary_union
+from shapely.geometry import LineString
 
 
 import gettext
 import gettext
 import FlatCAMTranslation as fcTranslate
 import FlatCAMTranslation as fcTranslate
@@ -136,7 +138,7 @@ class Panelize(FlatCAMTool):
         self.box_combo.is_last = True
         self.box_combo.is_last = True
 
 
         self.box_combo.setToolTip(
         self.box_combo.setToolTip(
-            _("The actual object that is used a container for the\n "
+            _("The actual object that is used as container for the\n "
               "selected object that is to be panelized.")
               "selected object that is to be panelized.")
         )
         )
         form_layout.addRow(self.box_combo)
         form_layout.addRow(self.box_combo)
@@ -402,19 +404,18 @@ class Panelize(FlatCAMTool):
     def on_panelize(self):
     def on_panelize(self):
         name = self.object_combo.currentText()
         name = self.object_combo.currentText()
 
 
-        # Get source object.
+        # Get source object to be panelized.
         try:
         try:
-            panel_obj = self.app.collection.get_by_name(str(name))
+            panel_source_obj = self.app.collection.get_by_name(str(name))
         except Exception as e:
         except Exception as e:
             log.debug("Panelize.on_panelize() --> %s" % str(e))
             log.debug("Panelize.on_panelize() --> %s" % str(e))
-            self.app.inform.emit('[ERROR_NOTCL] %s: %s' %
-                                 (_("Could not retrieve object"), name))
+            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), name))
             return "Could not retrieve object: %s" % name
             return "Could not retrieve object: %s" % name
 
 
-        if panel_obj is None:
+        if panel_source_obj is None:
             self.app.inform.emit('[ERROR_NOTCL] %s: %s' %
             self.app.inform.emit('[ERROR_NOTCL] %s: %s' %
-                                 (_("Object not found"), panel_obj))
-            return "Object not found: %s" % panel_obj
+                                 (_("Object not found"), panel_source_obj))
+            return "Object not found: %s" % panel_source_obj
 
 
         boxname = self.box_combo.currentText()
         boxname = self.box_combo.currentText()
 
 
@@ -422,17 +423,15 @@ class Panelize(FlatCAMTool):
             box = self.app.collection.get_by_name(boxname)
             box = self.app.collection.get_by_name(boxname)
         except Exception as e:
         except Exception as e:
             log.debug("Panelize.on_panelize() --> %s" % str(e))
             log.debug("Panelize.on_panelize() --> %s" % str(e))
-            self.app.inform.emit('[ERROR_NOTCL] %s: %s' %
-                                 (_("Could not retrieve object"), boxname))
+            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), boxname))
             return "Could not retrieve object: %s" % boxname
             return "Could not retrieve object: %s" % boxname
 
 
         if box is None:
         if box is None:
-            self.app.inform.emit('[WARNING_NOTCL]%s: %s' %
-                                 (_("No object Box. Using instead"), panel_obj))
+            self.app.inform.emit('[WARNING_NOTCL]%s: %s' % (_("No object Box. Using instead"), panel_source_obj))
             self.reference_radio.set_value('bbox')
             self.reference_radio.set_value('bbox')
 
 
         if self.reference_radio.get_value() == 'bbox':
         if self.reference_radio.get_value() == 'bbox':
-            box = panel_obj
+            box = panel_source_obj
 
 
         self.outname = name + '_panelized'
         self.outname = name + '_panelized'
 
 
@@ -478,20 +477,20 @@ class Panelize(FlatCAMTool):
                     rows -= 1
                     rows -= 1
                     panel_lengthy = ((ymax - ymin) * rows) + (spacing_rows * (rows - 1))
                     panel_lengthy = ((ymax - ymin) * rows) + (spacing_rows * (rows - 1))
 
 
-        if panel_obj.kind == 'excellon' or panel_obj.kind == 'geometry':
+        if panel_source_obj.kind == 'excellon' or panel_source_obj.kind == 'geometry':
             # make a copy of the panelized Excellon or Geometry tools
             # make a copy of the panelized Excellon or Geometry tools
             copied_tools = {}
             copied_tools = {}
-            for tt, tt_val in list(panel_obj.tools.items()):
+            for tt, tt_val in list(panel_source_obj.tools.items()):
                 copied_tools[tt] = deepcopy(tt_val)
                 copied_tools[tt] = deepcopy(tt_val)
 
 
-        if panel_obj.kind == 'gerber':
+        if panel_source_obj.kind == 'gerber':
             # make a copy of the panelized Gerber apertures
             # make a copy of the panelized Gerber apertures
             copied_apertures = {}
             copied_apertures = {}
-            for tt, tt_val in list(panel_obj.apertures.items()):
+            for tt, tt_val in list(panel_source_obj.apertures.items()):
                 copied_apertures[tt] = deepcopy(tt_val)
                 copied_apertures[tt] = deepcopy(tt_val)
 
 
-        def panelize_2():
-            if panel_obj is not None:
+        def panelize_worker():
+            if panel_source_obj is not None:
                 self.app.inform.emit(_("Generating panel ... "))
                 self.app.inform.emit(_("Generating panel ... "))
 
 
                 def job_init_excellon(obj_fin, app_obj):
                 def job_init_excellon(obj_fin, app_obj):
@@ -501,15 +500,15 @@ class Panelize(FlatCAMTool):
                     obj_fin.slots = []
                     obj_fin.slots = []
                     obj_fin.solid_geometry = []
                     obj_fin.solid_geometry = []
 
 
-                    for option in panel_obj.options:
+                    for option in panel_source_obj.options:
                         if option != 'name':
                         if option != 'name':
                             try:
                             try:
-                                obj_fin.options[option] = panel_obj.options[option]
+                                obj_fin.options[option] = panel_source_obj.options[option]
                             except KeyError:
                             except KeyError:
                                 log.warning("Failed to copy option. %s" % str(option))
                                 log.warning("Failed to copy option. %s" % str(option))
 
 
-                    geo_len_drills = len(panel_obj.drills) if panel_obj.drills else 0
-                    geo_len_slots = len(panel_obj.slots) if panel_obj.slots else 0
+                    geo_len_drills = len(panel_source_obj.drills) if panel_source_obj.drills else 0
+                    geo_len_slots = len(panel_source_obj.slots) if panel_source_obj.slots else 0
 
 
                     element = 0
                     element = 0
                     for row in range(rows):
                     for row in range(rows):
@@ -518,9 +517,9 @@ class Panelize(FlatCAMTool):
                             element += 1
                             element += 1
                             old_disp_number = 0
                             old_disp_number = 0
 
 
-                            if panel_obj.drills:
+                            if panel_source_obj.drills:
                                 drill_nr = 0
                                 drill_nr = 0
-                                for tool_dict in panel_obj.drills:
+                                for tool_dict in panel_source_obj.drills:
                                     if self.app.abort_flag:
                                     if self.app.abort_flag:
                                         # graceful abort requested by the user
                                         # graceful abort requested by the user
                                         raise grace
                                         raise grace
@@ -543,9 +542,9 @@ class Panelize(FlatCAMTool):
                                                                                   disp_number))
                                                                                   disp_number))
                                         old_disp_number = disp_number
                                         old_disp_number = disp_number
 
 
-                            if panel_obj.slots:
+                            if panel_source_obj.slots:
                                 slot_nr = 0
                                 slot_nr = 0
-                                for tool_dict in panel_obj.slots:
+                                for tool_dict in panel_source_obj.slots:
                                     if self.app.abort_flag:
                                     if self.app.abort_flag:
                                         # graceful abort requested by the user
                                         # graceful abort requested by the user
                                         raise grace
                                         raise grace
@@ -574,8 +573,8 @@ class Panelize(FlatCAMTool):
                         currenty += lenghty
                         currenty += lenghty
 
 
                     obj_fin.create_geometry()
                     obj_fin.create_geometry()
-                    obj_fin.zeros = panel_obj.zeros
-                    obj_fin.units = panel_obj.units
+                    obj_fin.zeros = panel_source_obj.zeros
+                    obj_fin.units = panel_source_obj.units
                     self.app.proc_container.update_view_text('')
                     self.app.proc_container.update_view_text('')
 
 
                 def job_init_geometry(obj_fin, app_obj):
                 def job_init_geometry(obj_fin, app_obj):
@@ -598,36 +597,36 @@ class Panelize(FlatCAMTool):
                     obj_fin.solid_geometry = []
                     obj_fin.solid_geometry = []
 
 
                     # create the initial structure on which to create the panel
                     # create the initial structure on which to create the panel
-                    if panel_obj.kind == 'geometry':
-                        obj_fin.multigeo = panel_obj.multigeo
+                    if panel_source_obj.kind == 'geometry':
+                        obj_fin.multigeo = panel_source_obj.multigeo
                         obj_fin.tools = copied_tools
                         obj_fin.tools = copied_tools
-                        if panel_obj.multigeo is True:
-                            for tool in panel_obj.tools:
+                        if panel_source_obj.multigeo is True:
+                            for tool in panel_source_obj.tools:
                                 obj_fin.tools[tool]['solid_geometry'][:] = []
                                 obj_fin.tools[tool]['solid_geometry'][:] = []
-                    elif panel_obj.kind == 'gerber':
+                    elif panel_source_obj.kind == 'gerber':
                         obj_fin.apertures = copied_apertures
                         obj_fin.apertures = copied_apertures
                         for ap in obj_fin.apertures:
                         for ap in obj_fin.apertures:
                             obj_fin.apertures[ap]['geometry'] = []
                             obj_fin.apertures[ap]['geometry'] = []
 
 
                     # find the number of polygons in the source solid_geometry
                     # find the number of polygons in the source solid_geometry
                     geo_len = 0
                     geo_len = 0
-                    if panel_obj.kind == 'geometry':
-                        if panel_obj.multigeo is True:
-                            for tool in panel_obj.tools:
+                    if panel_source_obj.kind == 'geometry':
+                        if panel_source_obj.multigeo is True:
+                            for tool in panel_source_obj.tools:
                                 try:
                                 try:
-                                    geo_len += len(panel_obj.tools[tool]['solid_geometry'])
+                                    geo_len += len(panel_source_obj.tools[tool]['solid_geometry'])
                                 except TypeError:
                                 except TypeError:
                                     geo_len += 1
                                     geo_len += 1
                         else:
                         else:
                             try:
                             try:
-                                geo_len = len(panel_obj.solid_geometry)
+                                geo_len = len(panel_source_obj.solid_geometry)
                             except TypeError:
                             except TypeError:
                                 geo_len = 1
                                 geo_len = 1
-                    elif panel_obj.kind == 'gerber':
-                        for ap in panel_obj.apertures:
-                            if 'geometry' in panel_obj.apertures[ap]:
+                    elif panel_source_obj.kind == 'gerber':
+                        for ap in panel_source_obj.apertures:
+                            if 'geometry' in panel_source_obj.apertures[ap]:
                                 try:
                                 try:
-                                    geo_len += len(panel_obj.apertures[ap]['geometry'])
+                                    geo_len += len(panel_source_obj.apertures[ap]['geometry'])
                                 except TypeError:
                                 except TypeError:
                                     geo_len += 1
                                     geo_len += 1
 
 
@@ -639,29 +638,23 @@ class Panelize(FlatCAMTool):
                             element += 1
                             element += 1
                             old_disp_number = 0
                             old_disp_number = 0
 
 
-                            if panel_obj.kind == 'geometry':
-                                if panel_obj.multigeo is True:
-                                    for tool in panel_obj.tools:
+                            # Will panelize a Geometry Object
+                            if panel_source_obj.kind == 'geometry':
+                                if panel_source_obj.multigeo is True:
+                                    for tool in panel_source_obj.tools:
                                         if self.app.abort_flag:
                                         if self.app.abort_flag:
                                             # graceful abort requested by the user
                                             # graceful abort requested by the user
                                             raise grace
                                             raise grace
 
 
-                                        # geo = translate_recursion(panel_obj.tools[tool]['solid_geometry'])
-                                        # if isinstance(geo, list):
-                                        #     obj_fin.tools[tool]['solid_geometry'] += geo
-                                        # else:
-                                        #     obj_fin.tools[tool]['solid_geometry'].append(geo)
-
                                         # calculate the number of polygons
                                         # calculate the number of polygons
-                                        geo_len = len(panel_obj.tools[tool]['solid_geometry'])
+                                        geo_len = len(panel_source_obj.tools[tool]['solid_geometry'])
                                         pol_nr = 0
                                         pol_nr = 0
-                                        for geo_el in panel_obj.tools[tool]['solid_geometry']:
+                                        for geo_el in panel_source_obj.tools[tool]['solid_geometry']:
                                             trans_geo = translate_recursion(geo_el)
                                             trans_geo = translate_recursion(geo_el)
                                             obj_fin.tools[tool]['solid_geometry'].append(trans_geo)
                                             obj_fin.tools[tool]['solid_geometry'].append(trans_geo)
 
 
                                             pol_nr += 1
                                             pol_nr += 1
                                             disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
                                             disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
-
                                             if old_disp_number < disp_number <= 100:
                                             if old_disp_number < disp_number <= 100:
                                                 self.app.proc_container.update_view_text(' %s: %d %d%%' %
                                                 self.app.proc_container.update_view_text(' %s: %d %d%%' %
                                                                                          (_("Copy"),
                                                                                          (_("Copy"),
@@ -669,23 +662,18 @@ class Panelize(FlatCAMTool):
                                                                                           disp_number))
                                                                                           disp_number))
                                                 old_disp_number = disp_number
                                                 old_disp_number = disp_number
                                 else:
                                 else:
-                                    # geo = translate_recursion(panel_obj.solid_geometry)
-                                    # if isinstance(geo, list):
-                                    #     obj_fin.solid_geometry += geo
-                                    # else:
-                                    #     obj_fin.solid_geometry.append(geo)
                                     if self.app.abort_flag:
                                     if self.app.abort_flag:
                                         # graceful abort requested by the user
                                         # graceful abort requested by the user
                                         raise grace
                                         raise grace
 
 
                                     try:
                                     try:
                                         # calculate the number of polygons
                                         # calculate the number of polygons
-                                        geo_len = len(panel_obj.solid_geometry)
+                                        geo_len = len(panel_source_obj.solid_geometry)
                                     except TypeError:
                                     except TypeError:
                                         geo_len = 1
                                         geo_len = 1
                                     pol_nr = 0
                                     pol_nr = 0
                                     try:
                                     try:
-                                        for geo_el in panel_obj.solid_geometry:
+                                        for geo_el in panel_source_obj.solid_geometry:
                                             if self.app.abort_flag:
                                             if self.app.abort_flag:
                                                 # graceful abort requested by the user
                                                 # graceful abort requested by the user
                                                 raise grace
                                                 raise grace
@@ -702,21 +690,18 @@ class Panelize(FlatCAMTool):
                                                                                           int(element),
                                                                                           int(element),
                                                                                           disp_number))
                                                                                           disp_number))
                                                 old_disp_number = disp_number
                                                 old_disp_number = disp_number
+
                                     except TypeError:
                                     except TypeError:
-                                        trans_geo = translate_recursion(panel_obj.solid_geometry)
+                                        trans_geo = translate_recursion(panel_source_obj.solid_geometry)
                                         obj_fin.solid_geometry.append(trans_geo)
                                         obj_fin.solid_geometry.append(trans_geo)
+                            # Will panelize a Gerber Object
                             else:
                             else:
-                                # geo = translate_recursion(panel_obj.solid_geometry)
-                                # if isinstance(geo, list):
-                                #     obj_fin.solid_geometry += geo
-                                # else:
-                                #     obj_fin.solid_geometry.append(geo)
                                 if self.app.abort_flag:
                                 if self.app.abort_flag:
                                     # graceful abort requested by the user
                                     # graceful abort requested by the user
                                     raise grace
                                     raise grace
 
 
                                 try:
                                 try:
-                                    for geo_el in panel_obj.solid_geometry:
+                                    for geo_el in panel_source_obj.solid_geometry:
                                         if self.app.abort_flag:
                                         if self.app.abort_flag:
                                             # graceful abort requested by the user
                                             # graceful abort requested by the user
                                             raise grace
                                             raise grace
@@ -724,21 +709,21 @@ class Panelize(FlatCAMTool):
                                         trans_geo = translate_recursion(geo_el)
                                         trans_geo = translate_recursion(geo_el)
                                         obj_fin.solid_geometry.append(trans_geo)
                                         obj_fin.solid_geometry.append(trans_geo)
                                 except TypeError:
                                 except TypeError:
-                                    trans_geo = translate_recursion(panel_obj.solid_geometry)
+                                    trans_geo = translate_recursion(panel_source_obj.solid_geometry)
                                     obj_fin.solid_geometry.append(trans_geo)
                                     obj_fin.solid_geometry.append(trans_geo)
 
 
-                                for apid in panel_obj.apertures:
+                                for apid in panel_source_obj.apertures:
                                     if self.app.abort_flag:
                                     if self.app.abort_flag:
                                         # graceful abort requested by the user
                                         # graceful abort requested by the user
                                         raise grace
                                         raise grace
-                                    if 'geometry' in panel_obj.apertures[apid]:
+                                    if 'geometry' in panel_source_obj.apertures[apid]:
                                         try:
                                         try:
                                             # calculate the number of polygons
                                             # calculate the number of polygons
-                                            geo_len = len(panel_obj.apertures[apid]['geometry'])
+                                            geo_len = len(panel_source_obj.apertures[apid]['geometry'])
                                         except TypeError:
                                         except TypeError:
                                             geo_len = 1
                                             geo_len = 1
                                         pol_nr = 0
                                         pol_nr = 0
-                                        for el in panel_obj.apertures[apid]['geometry']:
+                                        for el in panel_source_obj.apertures[apid]['geometry']:
                                             if self.app.abort_flag:
                                             if self.app.abort_flag:
                                                 # graceful abort requested by the user
                                                 # graceful abort requested by the user
                                                 raise grace
                                                 raise grace
@@ -771,20 +756,34 @@ class Panelize(FlatCAMTool):
                             currentx += lenghtx
                             currentx += lenghtx
                         currenty += lenghty
                         currenty += lenghty
 
 
+                    print("before", obj_fin.tools)
+                    if panel_source_obj.kind == 'geometry' and panel_source_obj.multigeo is True:
+                        # I'm going to do this only here as a fix for panelizing cutouts
+                        # I'm going to separate linestrings out of the solid geometry from other
+                        # possible type of elements and apply unary_union on them to fuse them
+                        for tool in obj_fin.tools:
+                            lines = []
+                            other_geo = []
+                            for geo in obj_fin.tools[tool]['solid_geometry']:
+                                if isinstance(geo, LineString):
+                                    lines.append(geo)
+                                else:
+                                    other_geo.append(geo)
+                            fused_lines = list(unary_union(lines))
+                            obj_fin.tools[tool]['solid_geometry'] = fused_lines + other_geo
+                    print("after", obj_fin.tools)
+
                     if panel_type == 'gerber':
                     if panel_type == 'gerber':
                         self.app.inform.emit('%s' % _("Generating panel ... Adding the Gerber code."))
                         self.app.inform.emit('%s' % _("Generating panel ... Adding the Gerber code."))
                         obj_fin.source_file = self.app.export_gerber(obj_name=self.outname, filename=None,
                         obj_fin.source_file = self.app.export_gerber(obj_name=self.outname, filename=None,
                                                                      local_use=obj_fin, use_thread=False)
                                                                      local_use=obj_fin, use_thread=False)
 
 
-                    # app_obj.log.debug("Found %s geometries. Creating a panel geometry cascaded union ..." %
-                    #                   len(obj_fin.solid_geometry))
-
                     # obj_fin.solid_geometry = cascaded_union(obj_fin.solid_geometry)
                     # obj_fin.solid_geometry = cascaded_union(obj_fin.solid_geometry)
                     # app_obj.log.debug("Finished creating a cascaded union for the panel.")
                     # app_obj.log.debug("Finished creating a cascaded union for the panel.")
                     self.app.proc_container.update_view_text('')
                     self.app.proc_container.update_view_text('')
 
 
                 self.app.inform.emit('%s: %d' % (_("Generating panel... Spawning copies"), (int(rows * columns))))
                 self.app.inform.emit('%s: %d' % (_("Generating panel... Spawning copies"), (int(rows * columns))))
-                if panel_obj.kind == 'excellon':
+                if panel_source_obj.kind == 'excellon':
                     self.app.new_object("excellon", self.outname, job_init_excellon, plot=True, autoselected=True)
                     self.app.new_object("excellon", self.outname, job_init_excellon, plot=True, autoselected=True)
                 else:
                 else:
                     self.app.new_object(panel_type, self.outname, job_init_geometry, plot=True, autoselected=True)
                     self.app.new_object(panel_type, self.outname, job_init_geometry, plot=True, autoselected=True)
@@ -801,7 +800,7 @@ class Panelize(FlatCAMTool):
 
 
         def job_thread(app_obj):
         def job_thread(app_obj):
             try:
             try:
-                panelize_2()
+                panelize_worker()
                 self.app.inform.emit('[success] %s' % _("Panel created successfully."))
                 self.app.inform.emit('[success] %s' % _("Panel created successfully."))
             except Exception as ee:
             except Exception as ee:
                 proc.done()
                 proc.done()

+ 5 - 10
flatcamTools/ToolPcbWizard.py

@@ -421,8 +421,7 @@ class PcbWizard(FlatCAMTool):
                 ret = excellon_obj.parse_file(file_obj=excellon_fileobj)
                 ret = excellon_obj.parse_file(file_obj=excellon_fileobj)
                 if ret == "fail":
                 if ret == "fail":
                     app_obj.log.debug("Excellon parsing failed.")
                     app_obj.log.debug("Excellon parsing failed.")
-                    app_obj.inform.emit('[ERROR_NOTCL] %s' %
-                                        _("This is not Excellon file."))
+                    app_obj.inform.emit('[ERROR_NOTCL] %s' % _("This is not Excellon file."))
                     return "fail"
                     return "fail"
             except IOError:
             except IOError:
                 app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Cannot parse file"), self.outname))
                 app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Cannot parse file"), self.outname))
@@ -443,8 +442,7 @@ class PcbWizard(FlatCAMTool):
             for tool in excellon_obj.tools:
             for tool in excellon_obj.tools:
                 if excellon_obj.tools[tool]['solid_geometry']:
                 if excellon_obj.tools[tool]['solid_geometry']:
                     return
                     return
-            app_obj.inform.emit('[ERROR_NOTCL] %s: %s' %
-                                (_("No geometry found in file"), name))
+            app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("No geometry found in file"), name))
             return "fail"
             return "fail"
 
 
         if excellon_fileobj is not None and excellon_fileobj != '':
         if excellon_fileobj is not None and excellon_fileobj != '':
@@ -463,12 +461,9 @@ class PcbWizard(FlatCAMTool):
                     self.app.file_opened.emit("excellon", name)
                     self.app.file_opened.emit("excellon", name)
 
 
                     # GUI feedback
                     # GUI feedback
-                    self.app.inform.emit('[success] %s: %s' %
-                                         (_("Imported"), name))
+                    self.app.inform.emit('[success] %s: %s' % (_("Imported"), name))
                     self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
                     self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
             else:
             else:
-                self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                     _('Excellon merging is in progress. Please wait...'))
+                self.app.inform.emit('[WARNING_NOTCL] %s' % _('Excellon merging is in progress. Please wait...'))
         else:
         else:
-            self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                 _('The imported Excellon file is None.'))
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _('The imported Excellon file is empty.'))

+ 1 - 1
flatcamTools/ToolSolderPaste.py

@@ -155,7 +155,7 @@ class SolderPaste(FlatCAMTool):
         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 below.")
         )
         )
         step1_description_lbl = QtWidgets.QLabel(_("Select tools.\n"
         step1_description_lbl = QtWidgets.QLabel(_("Select tools.\n"
                                                    "Modify parameters."))
                                                    "Modify parameters."))

+ 1 - 1
flatcamTools/ToolSub.py

@@ -97,7 +97,7 @@ class ToolSub(FlatCAMTool):
 
 
         form_layout.addRow(self.sub_gerber_label, self.sub_gerber_combo)
         form_layout.addRow(self.sub_gerber_label, self.sub_gerber_combo)
 
 
-        self.intersect_btn = FCButton(_('Substract Gerber'))
+        self.intersect_btn = FCButton(_('Subtract Gerber'))
         self.intersect_btn.setToolTip(
         self.intersect_btn.setToolTip(
             _("Will remove the area occupied by the subtractor\n"
             _("Will remove the area occupied by the subtractor\n"
               "Gerber from the Target Gerber.\n"
               "Gerber from the Target Gerber.\n"

BIN
locale/de/LC_MESSAGES/strings.mo


Разница между файлами не показана из-за своего большого размера
+ 206 - 200
locale/de/LC_MESSAGES/strings.po


BIN
locale/en/LC_MESSAGES/strings.mo


Разница между файлами не показана из-за своего большого размера
+ 207 - 201
locale/en/LC_MESSAGES/strings.po


BIN
locale/es/LC_MESSAGES/strings.mo


Разница между файлами не показана из-за своего большого размера
+ 206 - 200
locale/es/LC_MESSAGES/strings.po


BIN
locale/fr/LC_MESSAGES/strings.mo


Разница между файлами не показана из-за своего большого размера
+ 206 - 200
locale/fr/LC_MESSAGES/strings.po


BIN
locale/hu/LC_MESSAGES/strings.mo


Разница между файлами не показана из-за своего большого размера
+ 222 - 301
locale/hu/LC_MESSAGES/strings.po


BIN
locale/it/LC_MESSAGES/strings.mo


Разница между файлами не показана из-за своего большого размера
+ 720 - 754
locale/it/LC_MESSAGES/strings.po


BIN
locale/pt_BR/LC_MESSAGES/strings.mo


Разница между файлами не показана из-за своего большого размера
+ 206 - 200
locale/pt_BR/LC_MESSAGES/strings.po


BIN
locale/ro/LC_MESSAGES/strings.mo


Разница между файлами не показана из-за своего большого размера
+ 206 - 200
locale/ro/LC_MESSAGES/strings.po


BIN
locale/ru/LC_MESSAGES/strings.mo


Разница между файлами не показана из-за своего большого размера
+ 206 - 200
locale/ru/LC_MESSAGES/strings.po


+ 5 - 5
locale_template/strings.pot

@@ -6789,7 +6789,7 @@ msgstr ""
 #: flatcamGUI/ObjectUI.py:436
 #: flatcamGUI/ObjectUI.py:436
 msgid ""
 msgid ""
 "When the isolation geometry is generated,\n"
 "When the isolation geometry is generated,\n"
-"by checking this, the area of the object bellow\n"
+"by checking this, the area of the object below\n"
 "will be subtracted from the isolation geometry."
 "will be subtracted from the isolation geometry."
 msgstr ""
 msgstr ""
 
 
@@ -13014,7 +13014,7 @@ msgstr ""
 
 
 #: flatcamTools/ToolFilm.py:131
 #: flatcamTools/ToolFilm.py:131
 msgid ""
 msgid ""
-"The actual object that is used a container for the\n"
+"The actual object that is used as container for the\n"
 " selected object for which we create the film.\n"
 " selected object for which we create the film.\n"
 "Usually it is the PCB outline but it can be also the\n"
 "Usually it is the PCB outline but it can be also the\n"
 "same object for which the film is created."
 "same object for which the film is created."
@@ -14116,7 +14116,7 @@ msgid "Excellon merging is in progress. Please wait..."
 msgstr ""
 msgstr ""
 
 
 #: flatcamTools/ToolPcbWizard.py:474
 #: flatcamTools/ToolPcbWizard.py:474
-msgid "The imported Excellon file is None."
+msgid "The imported Excellon file is empty."
 msgstr ""
 msgstr ""
 
 
 #: flatcamTools/ToolProperties.py:131
 #: flatcamTools/ToolProperties.py:131
@@ -14604,7 +14604,7 @@ msgstr ""
 #: flatcamTools/ToolSolderPaste.py:158
 #: flatcamTools/ToolSolderPaste.py:158
 msgid ""
 msgid ""
 "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 below."
 msgstr ""
 msgstr ""
 
 
 #: flatcamTools/ToolSolderPaste.py:161
 #: flatcamTools/ToolSolderPaste.py:161
@@ -14800,7 +14800,7 @@ msgid ""
 msgstr ""
 msgstr ""
 
 
 #: flatcamTools/ToolSub.py:100
 #: flatcamTools/ToolSub.py:100
-msgid "Substract Gerber"
+msgid "Subtract Gerber"
 msgstr ""
 msgstr ""
 
 
 #: flatcamTools/ToolSub.py:102
 #: flatcamTools/ToolSub.py:102

+ 1 - 1
tclCommands/TclCommandJoinExcellon.py

@@ -34,7 +34,7 @@ class TclCommandJoinExcellon(TclCommand):
     help = {
     help = {
         'main': "Runs a merge operation (join) on the Excellon objects.\n"
         'main': "Runs a merge operation (join) on the Excellon objects.\n"
                 "The names of the Excellon objects to be merged will be entered after the outname,\n"
                 "The names of the Excellon objects to be merged will be entered after the outname,\n"
-                "separated by spaces. See the example bellow.\n"
+                "separated by spaces. See the example below.\n"
                 "WARNING: if the name of an Excellon objects has spaces, enclose the name with quotes.",
                 "WARNING: if the name of an Excellon objects has spaces, enclose the name with quotes.",
         'args': collections.OrderedDict([
         'args': collections.OrderedDict([
             ('outname', 'Name of the new Excellon Object made by joining of other Excellon objects. Required'),
             ('outname', 'Name of the new Excellon Object made by joining of other Excellon objects. Required'),

+ 1 - 1
tclCommands/TclCommandJoinGeometry.py

@@ -34,7 +34,7 @@ class TclCommandJoinGeometry(TclCommand):
     help = {
     help = {
         'main': "Runs a merge operation (join) on the Geometry objects.\n"
         'main': "Runs a merge operation (join) on the Geometry objects.\n"
                 "The names of the Geometry objects to be merged will be entered after the outname,\n"
                 "The names of the Geometry objects to be merged will be entered after the outname,\n"
-                "separated by spaces. See the example bellow.\n"
+                "separated by spaces. See the example below.\n"
                 "WARNING: if the name of an Geometry objects has spaces, enclose the name with quotes.",
                 "WARNING: if the name of an Geometry objects has spaces, enclose the name with quotes.",
         'args': collections.OrderedDict([
         'args': collections.OrderedDict([
             ('outname', 'Name of the new Geometry Object made by joining of other Geometry objects. Required'),
             ('outname', 'Name of the new Geometry Object made by joining of other Geometry objects. Required'),

Некоторые файлы не были показаны из-за большого количества измененных файлов