فهرست منبع

Merge branch 'Beta'

David Robertson 5 سال پیش
والد
کامیت
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 
 
 - 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 = 8.992
-    version_date = "2020/05/01"
+    version_date = "2020/05/03"
     beta = True
     engine = '3D'
 
@@ -432,6 +432,9 @@ class App(QtCore.QObject):
         # ################################# DEFAULTS - PREFERENCES STORAGE ###########################################
         # ############################################################################################################
         self.defaults = FlatCAMDefaults()
+
+        self.defaults["root_folder_path"] = self.app_home
+
         current_defaults_path = os.path.join(self.data_path, "current_defaults.FlatConfig")
         if user_defaults:
             self.defaults.load(filename=current_defaults_path)
@@ -503,7 +506,6 @@ class App(QtCore.QObject):
         QtCore.QObject.__init__(self)
 
         self.ui = FlatCAMGUI(self)
-        self.on_grid_snap_triggered(state=True)
 
         theme_settings = QtCore.QSettings("Open Source", "FlatCAM")
         if theme_settings.contains("theme"):
@@ -1071,9 +1073,6 @@ class App(QtCore.QObject):
         # 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.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
         self.close_app_signal.connect(self.kill_app)
         # ################################# FINISHED CONNECTING SIGNALS #############################################
@@ -2908,45 +2907,37 @@ class App(QtCore.QObject):
 
         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.
-        :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
         """
         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):
             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)
 
     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(''), 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' % "Mike Evans"), 8, 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('%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' % "Eric Varsanyi"), 67, 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>' % _("Corrections")), 0, 2)
                 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' % "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' % "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' % ""), 2, 2)
                 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' % "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.translators_tab_layout.addStretch()
 
@@ -4120,9 +4128,11 @@ class App(QtCore.QObject):
         obj.multigeo = True
         for tooluid, dict_value in obj.tools.items():
             dict_value['solid_geometry'] = deepcopy(obj.solid_geometry)
+
         if not isinstance(obj.solid_geometry, list):
             obj.solid_geometry = [obj.solid_geometry]
-        obj.solid_geometry[:] = []
+
+        # obj.solid_geometry[:] = []
         obj.plot()
 
         self.should_we_save = True
@@ -4639,7 +4649,7 @@ class App(QtCore.QObject):
         self.defaults.report_usage("on_toggle_grid()")
 
         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):
         self.defaults.report_usage("on_toggle_grd_lines()")
@@ -5033,7 +5043,7 @@ class App(QtCore.QObject):
                 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
-    # See description bellow.
+    # See description below.
     def on_delete_keypress(self):
         notebook_widget_name = self.ui.notebook.currentWidget().objectName()
 
@@ -6949,7 +6959,6 @@ class App(QtCore.QObject):
                 else:
 
                     key_modifier = QtWidgets.QApplication.keyboardModifiers()
-
                     if key_modifier == QtCore.Qt.ShiftModifier:
                         mod_key = 'Shift'
                     elif key_modifier == QtCore.Qt.ControlModifier:
@@ -6958,20 +6967,19 @@ class App(QtCore.QObject):
                         mod_key = None
 
                     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
                             # 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
                             # 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.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.delete_hover_shape()
+
+                            self.delete_hover_shape()
                     except Exception as e:
                         log.warning("FlatCAMApp.on_mouse_click_release_over_plot() select click --> Error: %s" % str(e))
                         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 #############
     # ###############################################################################################################
-
     def init_code_editor(self, name):
 
         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
             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
 
-        :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:
             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.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
         #
@@ -8372,9 +8376,9 @@ class App(QtCore.QObject):
         """
         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")
@@ -9609,27 +9613,46 @@ class App(QtCore.QObject):
         :param silent:      If True there will be no messages printed to StatusBar
         :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:
                 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
             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
             self.file_opened.emit("script", filename)
@@ -10611,29 +10634,6 @@ class App(QtCore.QObject):
                 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):
         """
         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)
             # 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):
         """
         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:
                         log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
                                   "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'
                 self.z_cut = deepcopy(old_zcut)
             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..."))
         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.
 
@@ -5025,8 +5022,7 @@ class CNCjob(Geometry):
 
         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)):
         """
 
@@ -5119,8 +5115,7 @@ class CNCjob(Geometry):
                 # For Incremental coordinates type G91
                 # next_x = pt[0] - prev_x
                 # 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_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
         "first_run": True,
         "units": "MM",
+        "root_folder_path": '',
         "global_serial": 0,
         "global_stats": dict(),
         "global_tabs_detachable": True,

+ 3 - 3
flatcamEditors/FlatCAMExcEditor.py

@@ -2837,7 +2837,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         # start with GRID toolbar activated
         if self.app.ui.grid_snap_btn.isChecked() is False:
             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.cmenu_newmenu.menuAction().setVisible(False)
@@ -3226,7 +3226,7 @@ class FlatCAMExcEditor(QtCore.QObject):
             spec = {"C": float(tool_dia[0])}
             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'] = []
 
             # 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])}
                 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'] = []
 
             # 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
         if self.app.ui.grid_snap_btn.isChecked() is False:
             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):
         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
 import shapely.affinity as affinity
 
+from vispy.geometry import Rect
+
 import threading
 import time
 from copy import copy, deepcopy
@@ -3701,7 +3703,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         # start with GRID toolbar activated
         if self.app.ui.grid_snap_btn.isChecked() is False:
             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
         self.app.ui.popmenu_edit.setVisible(False)
@@ -3721,6 +3723,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
         except Exception as e:
             log.debug("FlatCAMGrbEditor.deactivate_grb_editor() --> %s" % str(e))
 
+        self.clear()
+
         # adjust the status of the menu entries related to the editor
         self.app.ui.menueditedit.setDisabled(False)
         self.app.ui.menueditok.setDisabled(True)
@@ -3729,7 +3733,6 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.app.ui.popmenu_save.setVisible(False)
 
         self.disconnect_canvas_event_handlers()
-        self.clear()
         self.app.ui.grb_edit_toolbar.setDisabled(True)
 
         settings = QSettings("Open Source", "FlatCAM")
@@ -3937,8 +3940,12 @@ class FlatCAMGrbEditor(QtCore.QObject):
             pass
 
     def clear(self):
+        self.thread.quit()
+
         self.active_tool = None
         self.selected = []
+        self.storage_dict.clear()
+        self.results.clear()
 
         self.shapes.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'
         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
         orig_grb_obj.visible = False
@@ -4228,8 +4244,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         else:
             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
     def update_options(obj):
@@ -4927,6 +4942,39 @@ class FlatCAMGrbEditor(QtCore.QObject):
     #     self.app.app_cursor.enabled = False
     #     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):
         """
         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):
 
-    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.plain_text = plain_text

+ 30 - 5
flatcamGUI/FlatCAMGUI.py

@@ -2366,7 +2366,6 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         # ########################################################################
         # ######################## BUILD PREFERENCES #############################
         # ########################################################################
-
         self.general_defaults_form = GeneralPreferencesUI(decimals=self.decimals)
         self.gerber_defaults_form = GerberPreferencesUI(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 #################
         # ########################################################################
-
         flat_settings = QSettings("Open Source", "FlatCAM")
         if flat_settings.contains("saved_gui_state"):
             saved_gui_state = flat_settings.value('saved_gui_state')
@@ -2439,15 +2437,42 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
             del qsettings
 
         self.lock_toolbar(lock=lock_state)
+        self.on_grid_snap_triggered(state=True)
+
         self.lock_action.triggered[bool].connect(self.lock_toolbar)
 
         self.pref_open_button.clicked.connect(self.on_preferences_open_folder)
         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 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
         # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
+    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):
         """
         Filter the ToolTips display based on a Preferences setting
@@ -3207,7 +3232,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                         else:
                             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:
                     # make sure it works only for the Project Tab who is an instance of KeySensitiveListView
                     focused_wdg = QtWidgets.QApplication.focusWidget()
@@ -3811,10 +3836,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                         self.app.grb_editor.select_tool('track')
                         return
 
-                    # Zoom Fit
+                    # Zoom fit
                     if key == QtCore.Qt.Key_V or key == 'V':
                         self.app.grb_editor.launched_from_shortcuts = True
-                        self.app.on_zoom_fit(None)
+                        self.app.grb_editor.on_zoom_fit()
                         return
 
                 # 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
         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)
         self.defaults_form_fields = {
             # 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)
 
         # 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))
 
+    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
     def on_splash_changed(state):
         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.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_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']
 
         # 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.form_fields.update({

+ 26 - 0
flatcamObjects/FlatCAMGeometry.py

@@ -1114,6 +1114,26 @@ class GeometryObject(FlatCAMObj, Geometry):
             self.ui.tipanglelabel.show()
             self.ui.tipangle_entry.show()
             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()
         else:
@@ -1122,6 +1142,12 @@ class GeometryObject(FlatCAMObj, Geometry):
             self.ui.tipanglelabel.hide()
             self.ui.tipangle_entry.hide()
             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):
         vdia = float(self.ui.tipdia_entry.get_value())

+ 33 - 6
flatcamObjects/FlatCAMScript.py

@@ -50,15 +50,14 @@ class ScriptObject(FlatCAMObj):
 
         self.units = ''
 
+        self.script_editor_tab = None
+
         self.ser_attrs = ['options', 'kind', 'source_file']
         self.source_file = ''
         self.script_code = ''
 
         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):
         """
         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>'
             ))
 
+        self.script_editor_tab = TextEditor(app=self.app, plain_text=True, parent=self.app.ui)
+
         # 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()):
@@ -99,8 +100,8 @@ class ScriptObject(FlatCAMObj):
         #     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'])
+        # 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)
         # self.script_editor_tab.code_editor.clear()
@@ -111,7 +112,7 @@ class ScriptObject(FlatCAMObj):
 
         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)
 
         flt = "FlatCAM Scripts (*.FlatScript);;All Files (*.*)"
@@ -150,6 +151,32 @@ class ScriptObject(FlatCAMObj):
     def 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):
         # 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

+ 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
                                     # (those could be in another file like PCB Wizard do)
                                     # 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
                                     self.diameterless = True
                                     self.app.inform.emit('[WARNING] %s%s %s' %

+ 106 - 9
flatcamTools/ToolCutOut.py

@@ -293,12 +293,12 @@ class CutOut(FlatCAMTool):
                             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.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid0.addWidget(separator_line, 21, 0, 1, 2)
+        grid0.addWidget(separator_line, 22, 0, 1, 2)
 
         # Title5
         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"
               "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
         self.man_object_combo = FCComboBox()
@@ -322,8 +322,8 @@ class CutOut(FlatCAMTool):
         )
         # 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.setToolTip(
@@ -338,7 +338,7 @@ class CutOut(FlatCAMTool):
                             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.setToolTip(
@@ -354,7 +354,7 @@ class CutOut(FlatCAMTool):
                             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()
 
@@ -394,6 +394,9 @@ class CutOut(FlatCAMTool):
         self.x_pos = None
         self.y_pos = None
 
+        # store the default data for the resulting Geometry Object
+        self.default_data = {}
+
         # Signals
         self.ff_cutout_object_btn.clicked.connect(self.on_freeform_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.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):
         log.debug("Cutout.on_freeform_cutout() was launched ...")
 
@@ -622,8 +667,8 @@ class CutOut(FlatCAMTool):
 
                     solid_geo += cutout_handler(geom=geom_struct)
 
-            geo_obj.solid_geometry = deepcopy(solid_geo)
             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['ymin'] = ymin
             geo_obj.options['xmax'] = xmax
@@ -633,6 +678,23 @@ class CutOut(FlatCAMTool):
             geo_obj.options['multidepth'] = self.mpass_cb.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"
         self.app.new_object('geometry', outname, geo_init)
 
@@ -759,6 +821,7 @@ class CutOut(FlatCAMTool):
                 return proc_geometry
 
             if kind == 'single':
+                # fuse the lines
                 object_geo = unary_union(object_geo)
 
                 xmin, ymin, xmax, ymax = object_geo.bounds
@@ -805,11 +868,28 @@ class CutOut(FlatCAMTool):
                                          _("Rectangular cutout with negative margin is not possible."))
                     return "fail"
 
-            geo_obj.solid_geometry = deepcopy(solid_geo)
             geo_obj.options['cnctooldia'] = str(dia)
             geo_obj.options['cutz'] = self.cutz_entry.get_value()
             geo_obj.options['multidepth'] = self.mpass_cb.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"
         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['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"
         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.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"
               "Usually it is the PCB outline but it can be also the\n"
               "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 shapely.affinity as affinity
+from shapely.ops import unary_union
+from shapely.geometry import LineString
 
 import gettext
 import FlatCAMTranslation as fcTranslate
@@ -136,7 +138,7 @@ class Panelize(FlatCAMTool):
         self.box_combo.is_last = True
 
         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.")
         )
         form_layout.addRow(self.box_combo)
@@ -402,19 +404,18 @@ class Panelize(FlatCAMTool):
     def on_panelize(self):
         name = self.object_combo.currentText()
 
-        # Get source object.
+        # Get source object to be panelized.
         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:
             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
 
-        if panel_obj is None:
+        if panel_source_obj is None:
             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()
 
@@ -422,17 +423,15 @@ class Panelize(FlatCAMTool):
             box = self.app.collection.get_by_name(boxname)
         except Exception as 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
 
         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')
 
         if self.reference_radio.get_value() == 'bbox':
-            box = panel_obj
+            box = panel_source_obj
 
         self.outname = name + '_panelized'
 
@@ -478,20 +477,20 @@ class Panelize(FlatCAMTool):
                     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
             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)
 
-        if panel_obj.kind == 'gerber':
+        if panel_source_obj.kind == 'gerber':
             # make a copy of the panelized Gerber 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)
 
-        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 ... "))
 
                 def job_init_excellon(obj_fin, app_obj):
@@ -501,15 +500,15 @@ class Panelize(FlatCAMTool):
                     obj_fin.slots = []
                     obj_fin.solid_geometry = []
 
-                    for option in panel_obj.options:
+                    for option in panel_source_obj.options:
                         if option != 'name':
                             try:
-                                obj_fin.options[option] = panel_obj.options[option]
+                                obj_fin.options[option] = panel_source_obj.options[option]
                             except KeyError:
                                 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
                     for row in range(rows):
@@ -518,9 +517,9 @@ class Panelize(FlatCAMTool):
                             element += 1
                             old_disp_number = 0
 
-                            if panel_obj.drills:
+                            if panel_source_obj.drills:
                                 drill_nr = 0
-                                for tool_dict in panel_obj.drills:
+                                for tool_dict in panel_source_obj.drills:
                                     if self.app.abort_flag:
                                         # graceful abort requested by the user
                                         raise grace
@@ -543,9 +542,9 @@ class Panelize(FlatCAMTool):
                                                                                   disp_number))
                                         old_disp_number = disp_number
 
-                            if panel_obj.slots:
+                            if panel_source_obj.slots:
                                 slot_nr = 0
-                                for tool_dict in panel_obj.slots:
+                                for tool_dict in panel_source_obj.slots:
                                     if self.app.abort_flag:
                                         # graceful abort requested by the user
                                         raise grace
@@ -574,8 +573,8 @@ class Panelize(FlatCAMTool):
                         currenty += lenghty
 
                     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('')
 
                 def job_init_geometry(obj_fin, app_obj):
@@ -598,36 +597,36 @@ class Panelize(FlatCAMTool):
                     obj_fin.solid_geometry = []
 
                     # 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
-                        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'][:] = []
-                    elif panel_obj.kind == 'gerber':
+                    elif panel_source_obj.kind == 'gerber':
                         obj_fin.apertures = copied_apertures
                         for ap in obj_fin.apertures:
                             obj_fin.apertures[ap]['geometry'] = []
 
                     # find the number of polygons in the source solid_geometry
                     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:
-                                    geo_len += len(panel_obj.tools[tool]['solid_geometry'])
+                                    geo_len += len(panel_source_obj.tools[tool]['solid_geometry'])
                                 except TypeError:
                                     geo_len += 1
                         else:
                             try:
-                                geo_len = len(panel_obj.solid_geometry)
+                                geo_len = len(panel_source_obj.solid_geometry)
                             except TypeError:
                                 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:
-                                    geo_len += len(panel_obj.apertures[ap]['geometry'])
+                                    geo_len += len(panel_source_obj.apertures[ap]['geometry'])
                                 except TypeError:
                                     geo_len += 1
 
@@ -639,29 +638,23 @@ class Panelize(FlatCAMTool):
                             element += 1
                             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:
                                             # graceful abort requested by the user
                                             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
-                                        geo_len = len(panel_obj.tools[tool]['solid_geometry'])
+                                        geo_len = len(panel_source_obj.tools[tool]['solid_geometry'])
                                         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)
                                             obj_fin.tools[tool]['solid_geometry'].append(trans_geo)
 
                                             pol_nr += 1
                                             disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
-
                                             if old_disp_number < disp_number <= 100:
                                                 self.app.proc_container.update_view_text(' %s: %d %d%%' %
                                                                                          (_("Copy"),
@@ -669,23 +662,18 @@ class Panelize(FlatCAMTool):
                                                                                           disp_number))
                                                 old_disp_number = disp_number
                                 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:
                                         # graceful abort requested by the user
                                         raise grace
 
                                     try:
                                         # calculate the number of polygons
-                                        geo_len = len(panel_obj.solid_geometry)
+                                        geo_len = len(panel_source_obj.solid_geometry)
                                     except TypeError:
                                         geo_len = 1
                                     pol_nr = 0
                                     try:
-                                        for geo_el in panel_obj.solid_geometry:
+                                        for geo_el in panel_source_obj.solid_geometry:
                                             if self.app.abort_flag:
                                                 # graceful abort requested by the user
                                                 raise grace
@@ -702,21 +690,18 @@ class Panelize(FlatCAMTool):
                                                                                           int(element),
                                                                                           disp_number))
                                                 old_disp_number = disp_number
+
                                     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)
+                            # Will panelize a Gerber Object
                             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:
                                     # graceful abort requested by the user
                                     raise grace
 
                                 try:
-                                    for geo_el in panel_obj.solid_geometry:
+                                    for geo_el in panel_source_obj.solid_geometry:
                                         if self.app.abort_flag:
                                             # graceful abort requested by the user
                                             raise grace
@@ -724,21 +709,21 @@ class Panelize(FlatCAMTool):
                                         trans_geo = translate_recursion(geo_el)
                                         obj_fin.solid_geometry.append(trans_geo)
                                 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)
 
-                                for apid in panel_obj.apertures:
+                                for apid in panel_source_obj.apertures:
                                     if self.app.abort_flag:
                                         # graceful abort requested by the user
                                         raise grace
-                                    if 'geometry' in panel_obj.apertures[apid]:
+                                    if 'geometry' in panel_source_obj.apertures[apid]:
                                         try:
                                             # calculate the number of polygons
-                                            geo_len = len(panel_obj.apertures[apid]['geometry'])
+                                            geo_len = len(panel_source_obj.apertures[apid]['geometry'])
                                         except TypeError:
                                             geo_len = 1
                                         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:
                                                 # graceful abort requested by the user
                                                 raise grace
@@ -771,20 +756,34 @@ class Panelize(FlatCAMTool):
                             currentx += lenghtx
                         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':
                         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,
                                                                      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)
                     # app_obj.log.debug("Finished creating a cascaded union for the panel.")
                     self.app.proc_container.update_view_text('')
 
                 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)
                 else:
                     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):
             try:
-                panelize_2()
+                panelize_worker()
                 self.app.inform.emit('[success] %s' % _("Panel created successfully."))
             except Exception as ee:
                 proc.done()

+ 5 - 10
flatcamTools/ToolPcbWizard.py

@@ -421,8 +421,7 @@ class PcbWizard(FlatCAMTool):
                 ret = excellon_obj.parse_file(file_obj=excellon_fileobj)
                 if ret == "fail":
                     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"
             except IOError:
                 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:
                 if excellon_obj.tools[tool]['solid_geometry']:
                     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"
 
         if excellon_fileobj is not None and excellon_fileobj != '':
@@ -463,12 +461,9 @@ class PcbWizard(FlatCAMTool):
                     self.app.file_opened.emit("excellon", name)
 
                     # 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)
             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:
-            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.setToolTip(
             _("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"
                                                    "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)
 
-        self.intersect_btn = FCButton(_('Substract Gerber'))
+        self.intersect_btn = FCButton(_('Subtract Gerber'))
         self.intersect_btn.setToolTip(
             _("Will remove the area occupied by the subtractor\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
 msgid ""
 "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."
 msgstr ""
 
@@ -13014,7 +13014,7 @@ msgstr ""
 
 #: flatcamTools/ToolFilm.py:131
 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"
 "Usually it is the PCB outline but it can be also the\n"
 "same object for which the film is created."
@@ -14116,7 +14116,7 @@ msgid "Excellon merging is in progress. Please wait..."
 msgstr ""
 
 #: flatcamTools/ToolPcbWizard.py:474
-msgid "The imported Excellon file is None."
+msgid "The imported Excellon file is empty."
 msgstr ""
 
 #: flatcamTools/ToolProperties.py:131
@@ -14604,7 +14604,7 @@ msgstr ""
 #: flatcamTools/ToolSolderPaste.py:158
 msgid ""
 "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 ""
 
 #: flatcamTools/ToolSolderPaste.py:161
@@ -14800,7 +14800,7 @@ msgid ""
 msgstr ""
 
 #: flatcamTools/ToolSub.py:100
-msgid "Substract Gerber"
+msgid "Subtract Gerber"
 msgstr ""
 
 #: flatcamTools/ToolSub.py:102

+ 1 - 1
tclCommands/TclCommandJoinExcellon.py

@@ -34,7 +34,7 @@ class TclCommandJoinExcellon(TclCommand):
     help = {
         '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"
-                "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.",
         'args': collections.OrderedDict([
             ('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 = {
         '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"
-                "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.",
         'args': collections.OrderedDict([
             ('outname', 'Name of the new Geometry Object made by joining of other Geometry objects. Required'),

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است