Parcourir la source

jpcgt/flatcam/Beta слито с Beta

Camellan il y a 6 ans
Parent
commit
d083eb5f7b
77 fichiers modifiés avec 3035 ajouts et 2739 suppressions
  1. 145 120
      FlatCAMApp.py
  2. 26 14
      FlatCAMObj.py
  3. 0 1
      FlatCAMPostProc.py
  4. 23 4
      ObjectCollection.py
  5. 35 1
      README.md
  6. 51 55
      camlib.py
  7. 3 0
      descartes/patch.py
  8. 31 28
      flatcamEditors/FlatCAMExcEditor.py
  9. 86 158
      flatcamEditors/FlatCAMGeoEditor.py
  10. 88 90
      flatcamEditors/FlatCAMGrbEditor.py
  11. 39 26
      flatcamGUI/FlatCAMGUI.py
  12. 20 26
      flatcamGUI/GUIElements.py
  13. 0 2
      flatcamGUI/ObjectUI.py
  14. 1 1
      flatcamGUI/PlotCanvasLegacy.py
  15. 1 4
      flatcamGUI/PreferencesUI.py
  16. 4 1
      flatcamGUI/VisPyCanvas.py
  17. 2 0
      flatcamParsers/ParseDXF.py
  18. 0 1
      flatcamParsers/ParseDXF_Spline.py
  19. 21 7
      flatcamParsers/ParseExcellon.py
  20. 2 3
      flatcamParsers/ParseFont.py
  21. 37 20
      flatcamParsers/ParseGerber.py
  22. 1 1
      flatcamParsers/ParseSVG.py
  23. 4 3
      flatcamTools/ToolCalculators.py
  24. 14 3
      flatcamTools/ToolCutOut.py
  25. 9 2
      flatcamTools/ToolDblSided.py
  26. 9 4
      flatcamTools/ToolDistance.py
  27. 8 4
      flatcamTools/ToolDistanceMin.py
  28. 11 8
      flatcamTools/ToolFilm.py
  29. 2 2
      flatcamTools/ToolImage.py
  30. 6 3
      flatcamTools/ToolMove.py
  31. 31 10
      flatcamTools/ToolNonCopperClear.py
  32. 14 6
      flatcamTools/ToolOptimal.py
  33. 9 4
      flatcamTools/ToolPDF.py
  34. 27 9
      flatcamTools/ToolPaint.py
  35. 31 13
      flatcamTools/ToolPanelize.py
  36. 4 4
      flatcamTools/ToolPcbWizard.py
  37. 19 13
      flatcamTools/ToolProperties.py
  38. 11 6
      flatcamTools/ToolRulesCheck.py
  39. 1 1
      flatcamTools/ToolShell.py
  40. 1 1
      flatcamTools/ToolSolderPaste.py
  41. 27 19
      flatcamTools/ToolSub.py
  42. 5 3
      flatcamTools/ToolTransform.py
  43. BIN
      locale/de/LC_MESSAGES/strings.mo
  44. 289 262
      locale/de/LC_MESSAGES/strings.po
  45. BIN
      locale/en/LC_MESSAGES/strings.mo
  46. 312 284
      locale/en/LC_MESSAGES/strings.po
  47. BIN
      locale/es/LC_MESSAGES/strings.mo
  48. 324 319
      locale/es/LC_MESSAGES/strings.po
  49. BIN
      locale/fr/LC_MESSAGES/strings.mo
  50. 324 316
      locale/fr/LC_MESSAGES/strings.po
  51. BIN
      locale/pt_BR/LC_MESSAGES/strings.mo
  52. 322 316
      locale/pt_BR/LC_MESSAGES/strings.po
  53. BIN
      locale/ro/LC_MESSAGES/strings.mo
  54. 309 284
      locale/ro/LC_MESSAGES/strings.po
  55. BIN
      locale/ru/LC_MESSAGES/strings.mo
  56. 296 277
      locale/ru/LC_MESSAGES/strings.po
  57. BIN
      share/close_edit_file16.png
  58. BIN
      share/close_edit_file32.png
  59. BIN
      share/copy_file16.png
  60. BIN
      share/copy_file32.png
  61. BIN
      share/delete_file16.png
  62. BIN
      share/delete_file32.png
  63. BIN
      share/deselect_all32.png
  64. BIN
      share/edit_file16.png
  65. BIN
      share/edit_file32.png
  66. BIN
      share/new_file16.png
  67. BIN
      share/new_file32.png
  68. BIN
      share/new_file_exc16.png
  69. BIN
      share/new_file_exc32.png
  70. BIN
      share/new_file_geo16.png
  71. BIN
      share/new_file_geo32.png
  72. BIN
      share/new_file_grb16.png
  73. BIN
      share/new_file_grb32.png
  74. BIN
      share/panelize16.png
  75. BIN
      share/panelize32.png
  76. BIN
      share/project_save16.png
  77. BIN
      share/project_save32.png

+ 145 - 120
FlatCAMApp.py

@@ -9,6 +9,8 @@
 import urllib.request
 import urllib.parse
 import urllib.error
+import webbrowser
+
 import getopt
 import random
 import simplejson as json
@@ -21,7 +23,7 @@ import subprocess
 import ctypes
 
 import tkinter as tk
-from PyQt5 import QtPrintSupport, QtNetwork
+from PyQt5 import QtPrintSupport
 
 from contextlib import contextmanager
 import gc
@@ -40,10 +42,12 @@ import vispy.scene as scene
 # #######################################
 from ObjectCollection import *
 from FlatCAMObj import *
+from camlib import to_dict, dict2obj, ET, ParseError
+
 from flatcamGUI.PlotCanvas import *
 from flatcamGUI.PlotCanvasLegacy import *
-
 from flatcamGUI.FlatCAMGUI import *
+
 from FlatCAMCommon import LoudDict
 from FlatCAMPostProc import load_postprocessors
 
@@ -3061,7 +3065,7 @@ class App(QtCore.QObject):
                                        separator=True)
 
         self.panelize_tool = Panelize(self)
-        self.panelize_tool.install(icon=QtGui.QIcon('share/panel16.png'))
+        self.panelize_tool.install(icon=QtGui.QIcon('share/panelize16.png'))
 
         self.film_tool = Film(self)
         self.film_tool.install(icon=QtGui.QIcon('share/film16.png'))
@@ -3192,6 +3196,7 @@ class App(QtCore.QObject):
         self.ui.newexc_btn.triggered.connect(self.new_excellon_object)
         self.ui.editgeo_btn.triggered.connect(self.object2editor)
         self.ui.update_obj_btn.triggered.connect(lambda: self.editor2object())
+        self.ui.copy_btn.triggered.connect(self.on_copy_object)
         self.ui.delete_btn.triggered.connect(self.on_delete)
 
         self.ui.distance_btn.triggered.connect(lambda: self.distance_tool.run(toggle=True))
@@ -3228,6 +3233,9 @@ class App(QtCore.QObject):
         """
         self.report_usage("object2editor()")
 
+        # disable the objects menu as it may interfere with the Editors
+        self.ui.menuobjects.setDisabled(True)
+
         edited_object = self.collection.get_active()
 
         if isinstance(edited_object, FlatCAMGerber) or isinstance(edited_object, FlatCAMGeometry) or \
@@ -3316,6 +3324,9 @@ class App(QtCore.QObject):
         """
         self.report_usage("editor2object()")
 
+        # re-enable the objects menu that was disabled on entry in Editor mode
+        self.ui.menuobjects.setDisabled(False)
+
         # do not update a geometry or excellon object unless it comes out of an editor
         if self.call_source != 'app':
             edited_obj = self.collection.get_active()
@@ -4527,10 +4538,10 @@ class App(QtCore.QObject):
 
                 attributions_label = QtWidgets.QLabel(
                     _(
-                        'Some of the icons used are from the following sources: <BR>'
+                        'Some of the icons used are from the following sources:<br>'
                         '<div>Icons made by <a href="https://www.flaticon.com/authors/freepik" '
                         'title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/"             '
-                        'title="Flaticon">www.flaticon.com</a></div><br>'
+                        'title="Flaticon">www.flaticon.com</a></div>'
                         'Icons by <a target="_blank" href="https://icons8.com">Icons8</a>'
                     )
                 )
@@ -4650,6 +4661,10 @@ class App(QtCore.QObject):
                 self.prog_form_lay.addRow(QtWidgets.QLabel('%s' % "@mgix"))
 
                 self.translator_grid_lay = QtWidgets.QGridLayout()
+                self.translator_grid_lay.setColumnStretch(0, 0)
+                self.translator_grid_lay.setColumnStretch(1, 0)
+                self.translator_grid_lay.setColumnStretch(2, 1)
+                self.translator_grid_lay.setColumnStretch(3, 0)
 
                 # trans_widget = QtWidgets.QWidget()
                 # trans_widget.setLayout(self.translator_grid_lay)
@@ -4667,25 +4682,29 @@ class App(QtCore.QObject):
 
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('<b>%s</b>' % _("Language")), 0, 0)
                 self.translator_grid_lay.addWidget(QtWidgets.QLabel('<b>%s</b>' % _("Translator")), 0, 1)
-                self.translator_grid_lay.addWidget(QtWidgets.QLabel('<b>%s</b>' % _("E-mail")), 0, 2)
-                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Brasilian - Portuguese"), 1, 0)
+                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' % " "),  1, 2)
+                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-Translation)"), 2, 1)
-                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 2, 2)
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu"), 2, 1)
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "(Google-Translation)"), 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-Translation)"), 3, 1)
-                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 3, 2)
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu"), 3, 1)
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Jens Karstedt"), 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' % " "), 4, 2)
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 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, 2)
+                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-Translation)"), 6, 1)
-                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 6, 2)
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu"), 6, 1)
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "(Google-Translation)"), 6, 2)
+                self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 6, 3)
                 self.translator_grid_lay.setColumnStretch(0, 0)
                 self.translators_tab_layout.addStretch()
 
@@ -7922,9 +7941,12 @@ class App(QtCore.QObject):
             def add_act(name):
                 obj_for_icon = self.collection.get_by_name(name)
                 add_action = QtWidgets.QAction(parent=self.ui.menuobjects)
+                add_action.setCheckable(True)
                 add_action.setText(name)
                 add_action.setIcon(QtGui.QIcon(icon_files[obj_for_icon.kind]))
-                add_action.triggered.connect(lambda: self.collection.set_exclusive_active(name))
+                add_action.triggered.connect(
+                    lambda: self.collection.set_active(name) if add_action.isChecked() is True else
+                    self.collection.set_inactive(name))
                 self.ui.menuobjects.addAction(add_action)
 
             for name in gerber_list:
@@ -7950,6 +7972,17 @@ class App(QtCore.QObject):
             for name in doc_list:
                 add_act(name)
 
+            self.ui.menuobjects.addSeparator()
+            self.ui.menuobjects_selall = self.ui.menuobjects.addAction(
+                QtGui.QIcon('share/select_all.png'),
+                _('Select All')
+            )
+            self.ui.menuobjects_unselall = self.ui.menuobjects.addAction(
+                QtGui.QIcon('share/deselect_all32.png'),
+                _('Deselect All')
+            )
+            self.ui.menuobjects_selall.triggered.connect(lambda: self.on_objects_selection(True))
+            self.ui.menuobjects_unselall.triggered.connect(lambda: self.on_objects_selection(False))
 
         elif state == 'delete':
             for act in self.ui.menuobjects.actions():
@@ -7966,7 +7999,9 @@ class App(QtCore.QObject):
                     add_action = QtWidgets.QAction(parent=self.ui.menuobjects)
                     add_action.setText(obj.options['name'])
                     add_action.setIcon(QtGui.QIcon(icon_files[obj.kind]))
-                    add_action.triggered.connect(lambda: self.collection.set_exclusive_active(obj.options['name']))
+                    add_action.triggered.connect(
+                        lambda: self.collection.set_active(obj.options['name']) if add_action.isChecked() is True else
+                        self.collection.set_inactive(obj.options['name']))
 
                     self.ui.menuobjects.insertAction(act, add_action)
 
@@ -7984,6 +8019,39 @@ class App(QtCore.QObject):
                     pass
             self.ui.menuobjects.clear()
 
+            self.ui.menuobjects.addSeparator()
+            self.ui.menuobjects_selall = self.ui.menuobjects.addAction(
+                QtGui.QIcon('share/select_all.png'),
+                _('Select All')
+            )
+            self.ui.menuobjects_unselall = self.ui.menuobjects.addAction(
+                QtGui.QIcon('share/deselect_all32.png'),
+                _('Deselect All')
+            )
+            self.ui.menuobjects_selall.triggered.connect(lambda: self.on_objects_selection(True))
+            self.ui.menuobjects_unselall.triggered.connect(lambda: self.on_objects_selection(False))
+
+    def on_objects_selection(self, on_off):
+        obj_list = self.collection.get_names()
+
+        if on_off is True:
+            self.collection.set_all_active()
+            for act in self.ui.menuobjects.actions():
+                try:
+                    act.setChecked(True)
+                except:
+                    pass
+            if obj_list:
+                self.inform.emit('[selected] %s' % _("All objects are selected."))
+        else:
+            self.collection.set_all_inactive()
+            for act in self.ui.menuobjects.actions():
+                try:
+                    act.setChecked(False)
+                except:
+                    pass
+            self.inform.emit('%s' % _("Objects selection is cleared."))
+
     def grid_status(self):
         if self.ui.grid_snap_btn.isChecked():
             return True
@@ -8426,93 +8494,40 @@ class App(QtCore.QObject):
                         objects_under_the_click_list.append(obj.options['name'])
 
         try:
-            # If there is no element in the overlapped objects list then make everyone inactive
-            # because we selected "nothing"
-            self.collection.set_all_inactive()
-
-            # delete the possible selection box around a possible selected object
-            self.delete_selection_shape()
-
-            if not objects_under_the_click_list:
-
-                # and as a convenience move the focus to the Project tab because Selected tab is now empty but
-                # only when working on App
-                if self.call_source == 'app':
-                    if self.click_noproject is False:
-                        self.ui.notebook.setCurrentWidget(self.ui.project_tab)
-                    else:
-                        # restore auto open the Project Tab
-                        self.click_noproject = False
-
-                    # delete any text in the status bar, implicitly the last object name that was selected
-                    self.inform.emit("")
-                else:
-                    self.call_source = 'app'
-            else:
+            if objects_under_the_click_list:
+                curr_sel_obj = self.collection.get_active()
                 # case when there is only an object under the click and we toggle it
                 if len(objects_under_the_click_list) == 1:
-                    if self.collection.get_active() is None:
+                    if curr_sel_obj is None:
                         self.collection.set_active(objects_under_the_click_list[0])
-                        # create the selection box around the selected object
                         curr_sel_obj = self.collection.get_active()
+
+                        # create the selection box around the selected object
                         if self.defaults['global_selection_shape'] is True:
                             self.draw_selection_shape(curr_sel_obj)
 
-                        # self.inform.emit('[selected] %s: %s selected' %
-                        #                  (str(curr_sel_obj.kind).capitalize(), str(curr_sel_obj.options['name'])))
-                        if curr_sel_obj.kind == 'gerber':
-                            self.inform.emit(
-                                _('[selected]<span style="color:{color};">{name}</span> selected').format(
-                                    color='green', name=str(curr_sel_obj.options['name'])))
-                        elif curr_sel_obj.kind == 'excellon':
-                            self.inform.emit(
-                                _('[selected]<span style="color:{color};">{name}</span> selected').format(
-                                    color='brown', name=str(curr_sel_obj.options['name'])))
-                        elif curr_sel_obj.kind == 'cncjob':
-                            self.inform.emit(
-                                _('[selected]<span style="color:{color};">{name}</span> selected').format(
-                                    color='blue', name=str(curr_sel_obj.options['name'])))
-                        elif curr_sel_obj.kind == 'geometry':
-                            self.inform.emit(
-                                _('[selected]<span style="color:{color};">{name}</span> selected').format(
-                                    color='red', name=str(curr_sel_obj.options['name'])))
-
                     elif self.collection.get_active().options['name'] not in objects_under_the_click_list:
-                        self.collection.set_all_inactive()
+                        self.on_objects_selection(False)
                         self.delete_selection_shape()
+
                         self.collection.set_active(objects_under_the_click_list[0])
-                        # create the selection box around the selected object
                         curr_sel_obj = self.collection.get_active()
+
+                        # create the selection box around the selected object
                         if self.defaults['global_selection_shape'] is True:
                             self.draw_selection_shape(curr_sel_obj)
 
-                        # self.inform.emit('[selected] %s: %s selected' %
-                        #                  (str(curr_sel_obj.kind).capitalize(), str(curr_sel_obj.options['name'])))
-                        if curr_sel_obj.kind == 'gerber':
-                            self.inform.emit(
-                                _('[selected]<span style="color:{color};">{name}</span> selected').format(
-                                    color='green', name=str(curr_sel_obj.options['name'])))
-                        elif curr_sel_obj.kind == 'excellon':
-                            self.inform.emit(
-                                _('[selected]<span style="color:{color};">{name}</span> selected').format(
-                                    color='brown', name=str(curr_sel_obj.options['name'])))
-                        elif curr_sel_obj.kind == 'cncjob':
-                            self.inform.emit(
-                                _('[selected]<span style="color:{color};">{name}</span> selected').format(
-                                    color='blue', name=str(curr_sel_obj.options['name'])))
-                        elif curr_sel_obj.kind == 'geometry':
-                            self.inform.emit(
-                                _('[selected]<span style="color:{color};">{name}</span> selected').format(
-                                    color='red', name=str(curr_sel_obj.options['name'])))
+                        self.selected_message(curr_sel_obj=curr_sel_obj)
 
                     else:
-                        self.collection.set_all_inactive()
+                        self.on_objects_selection(False)
                         self.delete_selection_shape()
-                        if self.call_source == 'app':
-                            # delete any text in the status bar, implicitly the last object name that was selected
-                            self.inform.emit("")
-                        else:
+
+                        if self.call_source != 'app':
                             self.call_source = 'app'
+
+                    self.selected_message(curr_sel_obj=curr_sel_obj)
+
                 else:
                     # If there is no selected object
                     # make active the first element of the overlapped objects list
@@ -8527,9 +8542,9 @@ class App(QtCore.QObject):
                         name_sel_obj = objects_under_the_click_list[0]
                         self.collection.set_active(name_sel_obj)
                     else:
-                        name_sel_obj_idx = objects_under_the_click_list.index(name_sel_obj)
+                        sel_idx = objects_under_the_click_list.index(name_sel_obj)
                         self.collection.set_all_inactive()
-                        self.collection.set_active(objects_under_the_click_list[(name_sel_obj_idx + 1) %
+                        self.collection.set_active(objects_under_the_click_list[(sel_idx + 1) %
                                                                                 len(objects_under_the_click_list)])
 
                     curr_sel_obj = self.collection.get_active()
@@ -8539,30 +8554,44 @@ class App(QtCore.QObject):
                     if self.defaults['global_selection_shape'] is True:
                         self.draw_selection_shape(curr_sel_obj)
 
-                    # self.inform.emit('[selected] %s: %s selected' %
-                    #                  (str(curr_sel_obj.kind).capitalize(), str(curr_sel_obj.options['name'])))
-                    if curr_sel_obj.kind == 'gerber':
-                        self.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
-                            color='green', name=str(curr_sel_obj.options['name'])))
-                    elif curr_sel_obj.kind == 'excellon':
-                        self.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
-                            color='brown', name=str(curr_sel_obj.options['name'])))
-                    elif curr_sel_obj.kind == 'cncjob':
-                        self.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
-                            color='blue', name=str(curr_sel_obj.options['name'])))
-                    elif curr_sel_obj.kind == 'geometry':
-                        self.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
-                            color='red', name=str(curr_sel_obj.options['name'])))
-
-                    # for obj in self.collection.get_list():
-                    #     obj.plot()
-                    # curr_sel_obj.plot(color=self.FC_dark_blue, face_color=self.FC_light_blue)
-
-                    # TODO: on selected objects change the object colors and do not draw the selection box
-                    # self.plotcanvas.update() # this updates the canvas
+                    self.selected_message(curr_sel_obj=curr_sel_obj)
+
+            else:
+                # deselect everything
+                self.on_objects_selection(False)
+                # delete the possible selection box around a possible selected object
+                self.delete_selection_shape()
+
+                # and as a convenience move the focus to the Project tab because Selected tab is now empty but
+                # only when working on App
+                if self.call_source == 'app':
+                    if self.click_noproject is False:
+                        self.ui.notebook.setCurrentWidget(self.ui.project_tab)
+                    else:
+                        # restore auto open the Project Tab
+                        self.click_noproject = False
+
+                    # delete any text in the status bar, implicitly the last object name that was selected
+                    # self.inform.emit("")
+                else:
+                    self.call_source = 'app'
         except Exception as e:
-            log.error("[ERROR] Something went bad. %s" % str(e))
-            return
+            log.error("[ERROR] Something went bad in App.select_objects(). %s" % str(e))
+
+    def selected_message(self, curr_sel_obj):
+        if curr_sel_obj:
+            if curr_sel_obj.kind == 'gerber':
+                self.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
+                    color='green', name=str(curr_sel_obj.options['name'])))
+            elif curr_sel_obj.kind == 'excellon':
+                self.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
+                    color='brown', name=str(curr_sel_obj.options['name'])))
+            elif curr_sel_obj.kind == 'cncjob':
+                self.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
+                    color='blue', name=str(curr_sel_obj.options['name'])))
+            elif curr_sel_obj.kind == 'geometry':
+                self.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
+                    color='red', name=str(curr_sel_obj.options['name'])))
 
     def delete_hover_shape(self):
         self.hover_shapes.clear()
@@ -8621,6 +8650,9 @@ class App(QtCore.QObject):
         :return:
         """
 
+        if sel_obj is None:
+            return
+
         pt1 = (float(sel_obj.options['xmin']), float(sel_obj.options['ymin']))
         pt2 = (float(sel_obj.options['xmax']), float(sel_obj.options['ymin']))
         pt3 = (float(sel_obj.options['xmax']), float(sel_obj.options['ymax']))
@@ -8634,13 +8666,6 @@ class App(QtCore.QObject):
             sel_rect = sel_rect.buffer(-0.00393)
             sel_rect = sel_rect.buffer(0.00787)
 
-        # if color:
-        #     face = Color(color, alpha=0.2)
-        #     outline = Color(color, alpha=0.8)
-        # else:
-        #     face = Color(self.defaults['global_sel_fill'], alpha=0.2)
-        #     outline = Color(self.defaults['global_sel_line'], alpha=0.8)
-
         if color:
             face = color[:-2] + str(hex(int(0.2 * 255)))[2:]
             outline = color[:-2] + str(hex(int(0.8 * 255)))[2:]
@@ -9044,7 +9069,7 @@ class App(QtCore.QObject):
         try:
             filename, _f = QtWidgets.QFileDialog.getSaveFileName(
                 caption=_("Export SVG"),
-                directory=self.get_last_save_folder() + '/' + str(name),
+                directory=self.get_last_save_folder() + '/' + str(name) + '_svg',
                 filter=_filter)
         except TypeError:
             filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export SVG"), filter=_filter)

+ 26 - 14
FlatCAMObj.py

@@ -10,24 +10,33 @@
 # File modified by: Marius Stanciu                         #
 # ##########################################################
 
-from PyQt5.QtCore import Qt
-from PyQt5.QtGui import QTextDocument
+
+from shapely.geometry import Point, Polygon, MultiPolygon, MultiLineString, LineString, LinearRing
+from shapely.ops import cascaded_union
+import shapely.affinity as affinity
+
 import copy
+from copy import deepcopy
+from io import StringIO
+import traceback
 import inspect  # TODO: For debugging only.
 from datetime import datetime
 
 from flatcamEditors.FlatCAMTextEditor import TextEditor
-
 from flatcamGUI.ObjectUI import *
 from FlatCAMCommon import LoudDict
 from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
-from camlib import *
 from flatcamParsers.ParseExcellon import Excellon
 from flatcamParsers.ParseGerber import Gerber
+from camlib import Geometry, CNCjob
+import FlatCAMApp
 
-import itertools
 import tkinter as tk
-import sys
+import os, sys, itertools
+import ezdxf
+
+import math
+import numpy as np
 
 import gettext
 import FlatCAMTranslation as fcTranslate
@@ -66,7 +75,7 @@ class FlatCAMObj(QtCore.QObject):
     app = None
 
     # signal to plot a single object
-    plot_single_object = pyqtSignal()
+    plot_single_object = QtCore.pyqtSignal()
 
     def __init__(self, name):
         """
@@ -2804,10 +2813,13 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
 
         for tool in tools:
             if tooldia > self.tools[tool]["C"]:
-                self.app.inform.emit('[ERROR_NOTCL] %s %s: %s' % (
-                    _("Milling tool for DRILLS is larger than hole size. Cancelled.",
-                      str(tool))
-                ))
+                self.app.inform.emit(
+                    '[ERROR_NOTCL] %s %s: %s' % (
+                        _("Milling tool for DRILLS is larger than hole size. Cancelled."),
+                        _("Tool"),
+                        str(tool)
+                    )
+                )
                 return False, "Error: Milling tool is larger than hole."
 
         def geo_init(geo_obj, app_obj):
@@ -3510,21 +3522,21 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             offset_item = QtWidgets.QComboBox()
             for item in self.offset_item_options:
                 offset_item.addItem(item)
-            offset_item.setStyleSheet('background-color: rgb(255,255,255)')
+            # offset_item.setStyleSheet('background-color: rgb(255,255,255)')
             idx = offset_item.findText(tooluid_value['offset'])
             offset_item.setCurrentIndex(idx)
 
             type_item = QtWidgets.QComboBox()
             for item in self.type_item_options:
                 type_item.addItem(item)
-            type_item.setStyleSheet('background-color: rgb(255,255,255)')
+            # type_item.setStyleSheet('background-color: rgb(255,255,255)')
             idx = type_item.findText(tooluid_value['type'])
             type_item.setCurrentIndex(idx)
 
             tool_type_item = QtWidgets.QComboBox()
             for item in self.tool_type_item_options:
                 tool_type_item.addItem(item)
-                tool_type_item.setStyleSheet('background-color: rgb(255,255,255)')
+                # tool_type_item.setStyleSheet('background-color: rgb(255,255,255)')
             idx = tool_type_item.findText(tooluid_value['tool_type'])
             tool_type_item.setCurrentIndex(idx)
 

+ 0 - 1
FlatCAMPostProc.py

@@ -9,7 +9,6 @@
 from importlib.machinery import SourceFileLoader
 import os
 from abc import ABCMeta, abstractmethod
-from datetime import datetime
 import math
 
 # module-root dictionary of postprocessors

+ 23 - 4
ObjectCollection.py

@@ -11,13 +11,20 @@
 # File modified by: Marius Stanciu                         #
 # ##########################################################
 
+from PyQt5 import QtGui, QtCore, QtWidgets
+from PyQt5.QtCore import Qt, QSettings
+from PyQt5.QtGui import QColor
 # from PyQt5.QtCore import QModelIndex
-from FlatCAMObj import *
+
+from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry, FlatCAMExcellon, FlatCAMCNCjob, FlatCAMDocument, FlatCAMScript
 import inspect  # TODO: Remove
 import FlatCAMApp
-from PyQt5 import QtGui, QtCore, QtWidgets
-from PyQt5.QtCore import Qt, QSettings
-# import webbrowser
+
+import re
+import logging
+import collections
+from copy import deepcopy
+from numpy import Inf
 
 import gettext
 import FlatCAMTranslation as fcTranslate
@@ -27,6 +34,8 @@ fcTranslate.apply_language('strings')
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
+log = logging.getLogger('base')
+
 
 class KeySensitiveListView(QtWidgets.QTreeView):
     """
@@ -715,6 +724,16 @@ class ObjectCollection(QtCore.QAbstractItemModel):
             log.error("[ERROR] Cause: %s" % str(e))
             raise
 
+    def set_all_active(self):
+        """
+        Select all objects from the project list. This triggers the
+        list_selection_changed event and call on_list_selection_changed.
+
+        :return: None
+        """
+        for name in self.get_names():
+            self.set_active(name)
+
     def set_exclusive_active(self, name):
         """
         Make the object with the name in parameters the only selected object

+ 35 - 1
README.md

@@ -9,6 +9,40 @@ CAD program, and create G-Code for Isolation routing.
 
 =================================================
 
+18.10.2019
+
+- finished the update on the Google translated Spanish translation.
+- updated the new objects icons for Gerber, Geometry and Excellon
+- small import problem fixed
+- RELEASE 8.98
+
+17.10.2019
+
+- fixed a bug in milling holes due of a message wrongly formatted
+- added an translator email address
+- finished the update on German Google translation. Part of it was corrected by Jens Karstedt
+- finished the update of the Romanian translation.
+- finished the Objects menu by adding the ability of actions to be checked so they will show the selected status of the objects and by adding to actions to (de)select all objects
+- fixed and optimized the click selection on canvas
+- fixed Gerber parsing for very simple Gerber files that have only one Polygon but many LPC zones
+- fixed SVG export; fix bug #327
+- finished the update on French Google translation.
+
+16.10.2019
+
+- small update to Romanian translation files
+
+15.10.2019
+
+- adjusted the layout in NCC Tool
+- fixed bug in Panelization Tool for which in case of Excellon objects, the panel kept a reference to the source object which created issues when moving or disabling/enabling the plots
+- cleaned up the module imports throughout the app (the TclCommands are not yet verified)
+- removed the styling on the comboboxes cellWidget's in the Tool Tables
+- replaced some of the icons that did not looked Ok on the dark theme
+- added a new toolbar button for the Copy object functionality
+- changed the Panelize tool icon
+- corrected some strings
+
 14.10.2019
 
 - modified the result highlight color in Check Rules Tool
@@ -189,7 +223,7 @@ CAD program, and create G-Code for Isolation routing.
 - fixed the ToolMeasurement geometry not being displayed
 - fixed a bug in Excellon Editor that crashed the app when editing the first tool added automatically into a new black Excellon file
 - made sure that if the big mouse cursor is selected, the utility geometry in Excellon Editor has a thicker line width (2 pixels now) so it is visible over the geometry of the mouse cursor
-- fixed issue #319 where generating a CNCJob from a geometry made with NCC Tool made the app crash
+- fixed issue #319 where generating a CNCJob from a geometry made with NCC Tool made the app crash; also #328 which is the same
 - replaced in FlatCAM Tools and in FLatCAMObj.py  and in Editors all references to hardcoded decimals in string formats for tools with a variable declared in the __init__()
 - fixed a small bug that made app crash when the splash screen is disabled: it was trying to close it without being open
 

+ 51 - 55
camlib.py

@@ -11,12 +11,9 @@ from PyQt5 import QtWidgets
 from io import StringIO
 
 import numpy as np
-from numpy import arctan2, Inf, array, sqrt, pi, ceil, sin, cos, dot, float32, \
-    transpose
 from numpy.linalg import solve, norm
 
-import re, sys, os, platform
-import math
+import platform
 from copy import deepcopy
 
 import traceback
@@ -26,8 +23,8 @@ from rtree import index as rtindex
 from lxml import etree as ET
 
 # See: http://toblerity.org/shapely/manual.html
-from shapely.geometry import Polygon, LineString, Point, LinearRing, MultiLineString
-from shapely.geometry import MultiPoint, MultiPolygon
+from shapely.geometry import Polygon, LineString, Point, LinearRing, MultiLineString, MultiPoint, MultiPolygon
+
 from shapely.geometry import box as shply_box
 from shapely.ops import cascaded_union, unary_union, polygonize
 import shapely.affinity as affinity
@@ -64,7 +61,6 @@ import gettext
 import FlatCAMTranslation as fcTranslate
 import builtins
 
-
 fcTranslate.apply_language('strings')
 
 log = logging.getLogger('base2')
@@ -162,9 +158,9 @@ class ApertureMacro:
 
             # ## Variables
             # These are variables defined locally inside the macro. They can be
-            # numerical constant or defind in terms of previously define
+            # numerical constant or defined in terms of previously define
             # variables, which can be defined locally or in an aperture
-            # definition. All replacements ocurr here.
+            # definition. All replacements occur here.
             match = ApertureMacro.amvar_re.search(part)
             if match:
                 var = match.group(1)
@@ -336,8 +332,8 @@ class ApertureMacro:
         points = [(0, 0)]*nverts
 
         for i in range(nverts):
-            points[i] = (x + 0.5 * dia * cos(2*pi * i/nverts),
-                         y + 0.5 * dia * sin(2*pi * i/nverts))
+            points[i] = (x + 0.5 * dia * np.cos(2*np.pi * i/nverts),
+                         y + 0.5 * dia * np.sin(2*np.pi * i/nverts))
 
         poly = Polygon(points)
         poly_rotated = affinity.rotate(poly, angle, origin=(0, 0))
@@ -637,10 +633,10 @@ class Geometry(object):
 
         def bounds_rec(obj):
             if type(obj) is list:
-                minx = Inf
-                miny = Inf
-                maxx = -Inf
-                maxy = -Inf
+                minx = np.Inf
+                miny = np.Inf
+                maxx = -np.Inf
+                maxy = -np.Inf
 
                 for k in obj:
                     if type(k) is dict:
@@ -1155,7 +1151,7 @@ class Geometry(object):
             log.debug("Image import as monochrome.")
         else:
             mask_setting = (red <= mask[1]) + (green <= mask[2]) + (blue <= mask[3])
-            total = np.zeros(red.shape, dtype=float32)
+            total = np.zeros(red.shape, dtype=np.float32)
             for band in red, green, blue:
                 total += band
             total /= 3
@@ -1847,8 +1843,6 @@ class Geometry(object):
         :return: SVG Element
         """
 
-        geom = None
-
         # Make sure we see a Shapely Geometry class and not a list
         if str(type(self)) == "<class 'FlatCAMObj.FlatCAMGeometry'>":
             flat_geo = []
@@ -1873,6 +1867,8 @@ class Geometry(object):
             elif skew_reference == 'bottomright':
                 skew_ref = (xmax, ymin)
 
+        geom = geom_svg
+
         if scale_factor_x:
             geom = affinity.scale(geom_svg, scale_factor_x, 1.0)
         if scale_factor_y:
@@ -3298,10 +3294,10 @@ class CNCjob(Geometry):
 
         def bounds_rec(obj):
             if type(obj) is list:
-                minx = Inf
-                miny = Inf
-                maxx = -Inf
-                maxy = -Inf
+                minx = np.Inf
+                miny = np.Inf
+                maxx = -np.Inf
+                maxy = -np.Inf
 
                 for k in obj:
                     if type(k) is dict:
@@ -4049,9 +4045,9 @@ class CNCjob(Geometry):
                 arcdir = [None, None, "cw", "ccw"]
                 if current['G'] in [2, 3]:  # arc
                     center = [gobj['I'] + current['X'], gobj['J'] + current['Y']]
-                    radius = sqrt(gobj['I']**2 + gobj['J']**2)
-                    start = arctan2(-gobj['J'], -gobj['I'])
-                    stop = arctan2(-center[1] + y, -center[0] + x)
+                    radius = np.sqrt(gobj['I']**2 + gobj['J']**2)
+                    start = np.arctan2(-gobj['J'], -gobj['I'])
+                    stop = np.arctan2(-center[1] + y, -center[0] + x)
                     path += arc(center, radius, start, stop, arcdir[current['G']], int(self.steps_per_circle / 4))
 
             # Update current instruction
@@ -4710,10 +4706,10 @@ class CNCjob(Geometry):
 
         def bounds_rec(obj):
             if type(obj) is list:
-                minx = Inf
-                miny = Inf
-                maxx = -Inf
-                maxy = -Inf
+                minx = np.Inf
+                miny = np.Inf
+                maxx = -np.Inf
+                maxy = -np.Inf
 
                 for k in obj:
                     if type(k) is dict:
@@ -4742,15 +4738,15 @@ class CNCjob(Geometry):
 
             bounds_coords = bounds_rec(self.solid_geometry)
         else:
-            minx = Inf
-            miny = Inf
-            maxx = -Inf
-            maxy = -Inf
+            minx = np.Inf
+            miny = np.Inf
+            maxx = -np.Inf
+            maxy = -np.Inf
             for k, v in self.cnc_tools.items():
-                minx = Inf
-                miny = Inf
-                maxx = -Inf
-                maxy = -Inf
+                minx = np.Inf
+                miny = np.Inf
+                maxx = -np.Inf
+                maxy = -np.Inf
                 try:
                     for k in v['solid_geometry']:
                         minx_, miny_, maxx_, maxy_ = bounds_rec(k)
@@ -5186,10 +5182,10 @@ class CNCjob(Geometry):
 
 
 def get_bounds(geometry_list):
-    xmin = Inf
-    ymin = Inf
-    xmax = -Inf
-    ymax = -Inf
+    xmin = np.Inf
+    ymin = np.Inf
+    xmax = -np.Inf
+    ymax = -np.Inf
 
     for gs in geometry_list:
         try:
@@ -5229,33 +5225,33 @@ def arc(center, radius, start, stop, direction, steps_per_circ):
     da_sign = {"cw": -1.0, "ccw": 1.0}
     points = []
     if direction == "ccw" and stop <= start:
-        stop += 2 * pi
+        stop += 2 * np.pi
     if direction == "cw" and stop >= start:
-        stop -= 2 * pi
+        stop -= 2 * np.pi
     
     angle = abs(stop - start)
         
     # angle = stop-start
-    steps = max([int(ceil(angle / (2 * pi) * steps_per_circ)), 2])
+    steps = max([int(np.ceil(angle / (2 * np.pi) * steps_per_circ)), 2])
     delta_angle = da_sign[direction] * angle * 1.0 / steps
     for i in range(steps + 1):
         theta = start + delta_angle * i
-        points.append((center[0] + radius * cos(theta), center[1] + radius * sin(theta)))
+        points.append((center[0] + radius * np.cos(theta), center[1] + radius * np.sin(theta)))
     return points
 
 
 def arc2(p1, p2, center, direction, steps_per_circ):
-    r = sqrt((center[0] - p1[0]) ** 2 + (center[1] - p1[1]) ** 2)
-    start = arctan2(p1[1] - center[1], p1[0] - center[0])
-    stop = arctan2(p2[1] - center[1], p2[0] - center[0])
+    r = np.sqrt((center[0] - p1[0]) ** 2 + (center[1] - p1[1]) ** 2)
+    start = np.arctan2(p1[1] - center[1], p1[0] - center[0])
+    stop = np.arctan2(p2[1] - center[1], p2[0] - center[0])
     return arc(center, r, start, stop, direction, steps_per_circ)
 
 
 def arc_angle(start, stop, direction):
     if direction == "ccw" and stop <= start:
-        stop += 2 * pi
+        stop += 2 * np.pi
     if direction == "cw" and stop >= start:
-        stop -= 2 * pi
+        stop -= 2 * np.pi
 
     angle = abs(stop - start)
     return angle
@@ -5665,12 +5661,12 @@ def three_point_circle(p1, p2, p3):
     a2 = (p2 + p3) / 2.0
 
     # Normals
-    b1 = dot((p2 - p1), array([[0, -1], [1, 0]], dtype=float32))
-    b2 = dot((p3 - p2), array([[0, 1], [-1, 0]], dtype=float32))
+    b1 = np.dot((p2 - p1), np.array([[0, -1], [1, 0]], dtype=np.float32))
+    b2 = np.dot((p3 - p2), np.array([[0, 1], [-1, 0]], dtype=np.float32))
 
     # Params
     try:
-        T = solve(transpose(array([-b1, b2])), a1 - a2)
+        T = solve(np.transpose(np.array([-b1, b2])), a1 - a2)
     except Exception as e:
         log.debug("camlib.three_point_circle() --> %s" % str(e))
         return
@@ -5685,11 +5681,11 @@ def three_point_circle(p1, p2, p3):
 
 
 def distance(pt1, pt2):
-    return sqrt((pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2)
+    return np.sqrt((pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2)
 
 
 def distance_euclidian(x1, y1, x2, y2):
-    return sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
+    return np.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
 
 
 class FlatCAMRTree(object):

+ 3 - 0
descartes/patch.py

@@ -12,14 +12,17 @@ class Polygon(object):
             self.context = context
         else:
             self.context = getattr(context, '__geo_interface__', context)
+
     @property
     def geom_type(self):
         return (getattr(self.context, 'geom_type', None)
                 or self.context['type'])
+
     @property
     def exterior(self):
         return (getattr(self.context, 'exterior', None) 
                 or self.context['coordinates'][0])
+
     @property
     def interiors(self):
         value = getattr(self.context, 'interiors', None)

+ 31 - 28
flatcamEditors/FlatCAMExcEditor.py

@@ -8,19 +8,23 @@
 from PyQt5 import QtGui, QtCore, QtWidgets
 from PyQt5.QtCore import Qt, QSettings
 
-from shapely.geometry import LineString, LinearRing, MultiLineString
-from shapely.ops import cascaded_union
-import shapely.affinity as affinity
-
-from numpy import arctan2, Inf, array, sqrt, sign, dot
-from rtree import index as rtindex
-
-from camlib import *
+from camlib import distance, arc, FlatCAMRTreeStorage
 from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, LengthEntry, RadioSet, SpinBoxDelegate
 from flatcamEditors.FlatCAMGeoEditor import FCShapeTool, DrawTool, DrawToolShape, DrawToolUtilityShape, FlatCAMGeoEditor
 from flatcamParsers.ParseExcellon import Excellon
+import FlatCAMApp
+
+from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon, Point
+import shapely.affinity as affinity
+
+import numpy as np
+
+from rtree import index as rtindex
 
-from copy import copy, deepcopy
+import traceback
+import math
+import logging
+from copy import deepcopy
 
 import gettext
 import FlatCAMTranslation as fcTranslate
@@ -30,6 +34,8 @@ fcTranslate.apply_language('strings')
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
+log = logging.getLogger('base')
+
 
 class FCDrillAdd(FCShapeTool):
     """
@@ -556,7 +562,6 @@ class FCSlotArray(FCShapeTool):
                                           _("To add an Slot Array first select a tool in Tool Table"))
             return
 
-
         try:
             QtGui.QGuiApplication.restoreOverrideCursor()
         except Exception as e:
@@ -1006,7 +1011,6 @@ class FCDrillResize(FCShapeTool):
 
                         sel_shapes_to_be_deleted.append(select_shape)
 
-
                         # a hack to make the tool_table display more drills/slots per diameter when shape(drill/slot)
                         # is added.
                         # self.points_edit it's only useful first time when we load the data into the storage
@@ -3084,7 +3088,6 @@ class FlatCAMExcEditor(QtCore.QObject):
         # element[1] of the tuple is a list of coordinates (a tuple themselves)
         ordered_edited_points = sorted(zip(edited_points.keys(), edited_points.values()))
 
-
         current_tool = 0
         for tool_dia in ordered_edited_points:
             current_tool += 1
@@ -3208,14 +3211,13 @@ class FlatCAMExcEditor(QtCore.QObject):
             except KeyError:
                 self.app.inform.emit('[ERROR_NOTCL] %s' %
                                      _("There are no Tools definitions in the file. Aborting Excellon creation.")
-                )
+                                     )
             except:
                 msg = '[ERROR] %s' % \
                       _("An internal error has ocurred. See Shell.\n")
                 msg += traceback.format_exc()
                 app_obj.inform.emit(msg)
-                raise
-                # raise
+                return
 
         with self.app.proc_container.new(_("Creating Excellon.")):
 
@@ -3320,7 +3322,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         self.pos = self.canvas.translate_coords(event_pos)
 
         if self.app.grid_status() == True:
-            self.pos  = self.app.geo_editor.snap(self.pos[0], self.pos[1])
+            self.pos = self.app.geo_editor.snap(self.pos[0], self.pos[1])
         else:
             self.pos = (self.pos[0], self.pos[1])
 
@@ -3555,14 +3557,14 @@ class FlatCAMExcEditor(QtCore.QObject):
             for storage in self.storage_dict:
                 for obj in self.storage_dict[storage].get_objects():
                     if (sel_type is True and poly_selection.contains(obj.geo)) or \
-                            (sel_type is False and poly_selection.intersects(obj.geo)):
+                        (sel_type is False and poly_selection.intersects(obj.geo)):
 
-                            if obj in self.selected:
-                                # remove the shape object from the selected shapes storage
-                                self.selected.remove(obj)
-                            else:
-                                # add the shape object to the selected shapes storage
-                                self.selected.append(obj)
+                        if obj in self.selected:
+                            # remove the shape object from the selected shapes storage
+                            self.selected.remove(obj)
+                        else:
+                            # add the shape object to the selected shapes storage
+                            self.selected.append(obj)
         else:
             # clear the selection shapes storage
             self.selected = []
@@ -3878,7 +3880,8 @@ class FlatCAMExcEditor(QtCore.QObject):
                     # self.points_edit it's only useful first time when we load the data into the storage
                     # but is still used as referecen when building tool_table in self.build_ui()
                     # the number of drills displayed in column 2 is just a len(self.points_edit) therefore
-                    # deleting self.points_edit elements (doesn't matter who but just the number) solved the display issue.
+                    # deleting self.points_edit elements (doesn't matter who but just the number)
+                    # solved the display issue.
                     del self.points_edit[storage][0]
                 else:
                     self.storage_dict[storage].remove(del_shape)
@@ -3998,10 +4001,10 @@ class FlatCAMExcEditor(QtCore.QObject):
 
 
 def get_shapely_list_bounds(geometry_list):
-    xmin = Inf
-    ymin = Inf
-    xmax = -Inf
-    ymax = -Inf
+    xmin = np.Inf
+    ymin = np.Inf
+    xmax = -np.Inf
+    ymax = -np.Inf
 
     for gs in geometry_list:
         try:

+ 86 - 158
flatcamEditors/FlatCAMGeoEditor.py

@@ -13,24 +13,26 @@
 
 from PyQt5 import QtGui, QtCore, QtWidgets
 from PyQt5.QtCore import Qt, QSettings
-from camlib import *
+
+from camlib import distance, arc, three_point_circle, Geometry, FlatCAMRTreeStorage
 from FlatCAMTool import FlatCAMTool
-from flatcamGUI.ObjectUI import LengthEntry, RadioSet
+from flatcamGUI.ObjectUI import RadioSet
+from flatcamGUI.GUIElements import OptionalInputSection, FCCheckBox, FCEntry, FCComboBox, FCTextAreaRich, \
+    FCTable, FCDoubleSpinner, FCButton, EvalEntry2, FCInputDialog
+from flatcamParsers.ParseFont import *
+import FlatCAMApp
 
 from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon
-# from shapely.geometry import mapping
 from shapely.ops import cascaded_union, unary_union
 import shapely.affinity as affinity
 from shapely.geometry.polygon import orient
 
-from numpy import arctan2, Inf, array, sqrt, sign, dot
+import numpy as np
 from numpy.linalg import norm as numpy_norm
 
 from rtree import index as rtindex
-from flatcamGUI.GUIElements import OptionalInputSection, FCCheckBox, FCEntry, FCComboBox, FCTextAreaRich, \
-    FCTable, FCDoubleSpinner, FCButton, EvalEntry2, FCInputDialog
-from flatcamParsers.ParseFont import *
 
+from copy import deepcopy
 # from vispy.io import read_png
 import gettext
 import FlatCAMTranslation as fcTranslate
@@ -684,8 +686,8 @@ class TransformEditorTool(FlatCAMTool):
         self.rotate_button.set_value(_("Rotate"))
         self.rotate_button.setToolTip(
             _("Rotate the selected shape(s).\n"
-            "The point of reference is the middle of\n"
-            "the bounding box for all selected shapes.")
+              "The point of reference is the middle of\n"
+              "the bounding box for all selected shapes.")
         )
         self.rotate_button.setFixedWidth(60)
 
@@ -802,7 +804,7 @@ class TransformEditorTool(FlatCAMTool):
         self.scale_link_cb.setText(_("Link"))
         self.scale_link_cb.setToolTip(
             _("Scale the selected shape(s)\n"
-             "using the Scale Factor X for both axis."))
+              "using the Scale Factor X for both axis."))
         self.scale_link_cb.setFixedWidth(50)
 
         self.scale_zero_ref_cb = FCCheckBox()
@@ -1060,7 +1062,6 @@ class TransformEditorTool(FlatCAMTool):
                                  _("Transformation cancelled. No shape selected."))
             return
 
-
         self.draw_app.select_tool("select")
         self.app.ui.notebook.setTabText(2, "Tools")
         self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
@@ -1081,10 +1082,7 @@ class TransformEditorTool(FlatCAMTool):
                     self.app.inform.emit('[ERROR_NOTCL] %s' %
                                          _("Wrong value format entered, use a number."))
                     return
-        self.app.worker_task.emit({'fcn': self.on_rotate_action,
-                                       'params': [value]})
-        # self.on_rotate_action(value)
-        return
+        self.app.worker_task.emit({'fcn': self.on_rotate_action, 'params': [value]})
 
     def on_flipx(self):
         # self.on_flip("Y")
@@ -1205,13 +1203,9 @@ class TransformEditorTool(FlatCAMTool):
         axis = 'Y'
         point = (0, 0)
         if self.scale_zero_ref_cb.get_value():
-            self.app.worker_task.emit({'fcn': self.on_scale,
-                                       'params': [axis, xvalue, yvalue, point]})
-            # self.on_scale("Y", xvalue, yvalue, point=(0,0))
+            self.app.worker_task.emit({'fcn': self.on_scale, 'params': [axis, xvalue, yvalue, point]})
         else:
-            # self.on_scale("Y", xvalue, yvalue)
-            self.app.worker_task.emit({'fcn': self.on_scale,
-                                       'params': [axis, xvalue, yvalue]})
+            self.app.worker_task.emit({'fcn': self.on_scale, 'params': [axis, xvalue, yvalue]})
 
         return
 
@@ -1304,7 +1298,7 @@ class TransformEditorTool(FlatCAMTool):
 
                 except Exception as e:
                     self.app.inform.emit('[ERROR_NOTCL] %s: %s' %
-                                         (_("Rotation action was not executed"),str(e)))
+                                         (_("Rotation action was not executed"), str(e)))
                     return
 
     def on_flip(self, axis):
@@ -1664,10 +1658,10 @@ class DrawToolShape(object):
         # now it can get bounds for nested lists of objects
         def bounds_rec(shape_el):
             if type(shape_el) is list:
-                minx = Inf
-                miny = Inf
-                maxx = -Inf
-                maxy = -Inf
+                minx = np.Inf
+                miny = np.Inf
+                maxx = -np.Inf
+                maxy = -np.Inf
 
                 for k in shape_el:
                     minx_, miny_, maxx_, maxy_ = bounds_rec(k)
@@ -1904,10 +1898,10 @@ class DrawTool(object):
     def bounds(self, obj):
         def bounds_rec(o):
             if type(o) is list:
-                minx = Inf
-                miny = Inf
-                maxx = -Inf
-                maxy = -Inf
+                minx = np.Inf
+                miny = np.Inf
+                maxx = -np.Inf
+                maxy = -np.Inf
 
                 for k in o:
                     try:
@@ -1977,7 +1971,7 @@ class FCCircle(FCShapeTool):
         if len(self.points) == 1:
             p1 = self.points[0]
             p2 = data
-            radius = sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)
+            radius = np.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)
             return DrawToolUtilityShape(Point(p1).buffer(radius, int(self.steps_per_circ / 4)))
 
         return None
@@ -2086,36 +2080,36 @@ class FCArc(FCShapeTool):
                 p1 = self.points[1]
                 p2 = data
 
-                radius = sqrt((center[0] - p1[0]) ** 2 + (center[1] - p1[1]) ** 2)
-                startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
-                stopangle = arctan2(p2[1] - center[1], p2[0] - center[0])
+                radius = np.sqrt((center[0] - p1[0]) ** 2 + (center[1] - p1[1]) ** 2)
+                startangle = np.arctan2(p1[1] - center[1], p1[0] - center[0])
+                stopangle = np.arctan2(p2[1] - center[1], p2[0] - center[0])
 
                 return DrawToolUtilityShape([LineString(arc(center, radius, startangle, stopangle,
                                                             self.direction, self.steps_per_circ)),
                                              Point(center)])
 
             elif self.mode == '132':
-                p1 = array(self.points[0])
-                p3 = array(self.points[1])
-                p2 = array(data)
+                p1 = np.array(self.points[0])
+                p3 = np.array(self.points[1])
+                p2 = np.array(data)
 
                 try:
                     center, radius, t = three_point_circle(p1, p2, p3)
                 except TypeError:
                     return
 
-                direction = 'cw' if sign(t) > 0 else 'ccw'
+                direction = 'cw' if np.sign(t) > 0 else 'ccw'
 
-                startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
-                stopangle = arctan2(p3[1] - center[1], p3[0] - center[0])
+                startangle = np.arctan2(p1[1] - center[1], p1[0] - center[0])
+                stopangle = np.arctan2(p3[1] - center[1], p3[0] - center[0])
 
                 return DrawToolUtilityShape([LineString(arc(center, radius, startangle, stopangle,
                                                             direction, self.steps_per_circ)),
                                              Point(center), Point(p1), Point(p3)])
 
             else:  # '12c'
-                p1 = array(self.points[0])
-                p2 = array(self.points[1])
+                p1 = np.array(self.points[0])
+                p2 = np.array(self.points[1])
 
                 # Midpoint
                 a = (p1 + p2) / 2.0
@@ -2124,7 +2118,7 @@ class FCArc(FCShapeTool):
                 c = p2 - p1
 
                 # Perpendicular vector
-                b = dot(c, array([[0, -1], [1, 0]], dtype=float32))
+                b = np.dot(c, np.array([[0, -1], [1, 0]], dtype=np.float32))
                 b /= numpy_norm(b)
 
                 # Distance
@@ -2133,14 +2127,14 @@ class FCArc(FCShapeTool):
                 # Which side? Cross product with c.
                 # cross(M-A, B-A), where line is AB and M is test point.
                 side = (data[0] - p1[0]) * c[1] - (data[1] - p1[1]) * c[0]
-                t *= sign(side)
+                t *= np.sign(side)
 
                 # Center = a + bt
                 center = a + b * t
 
                 radius = numpy_norm(center - p1)
-                startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
-                stopangle = arctan2(p2[1] - center[1], p2[0] - center[0])
+                startangle = np.arctan2(p1[1] - center[1], p1[0] - center[0])
+                stopangle = np.arctan2(p2[1] - center[1], p2[0] - center[0])
 
                 return DrawToolUtilityShape([LineString(arc(center, radius, startangle, stopangle,
                                                             self.direction, self.steps_per_circ)),
@@ -2156,29 +2150,29 @@ class FCArc(FCShapeTool):
             p2 = self.points[2]
 
             radius = distance(center, p1)
-            startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
-            stopangle = arctan2(p2[1] - center[1], p2[0] - center[0])
+            startangle = np.arctan2(p1[1] - center[1], p1[0] - center[0])
+            stopangle = np.arctan2(p2[1] - center[1], p2[0] - center[0])
             self.geometry = DrawToolShape(LineString(arc(center, radius, startangle, stopangle,
                                                          self.direction, self.steps_per_circ)))
 
         elif self.mode == '132':
-            p1 = array(self.points[0])
-            p3 = array(self.points[1])
-            p2 = array(self.points[2])
+            p1 = np.array(self.points[0])
+            p3 = np.array(self.points[1])
+            p2 = np.array(self.points[2])
 
             center, radius, t = three_point_circle(p1, p2, p3)
-            direction = 'cw' if sign(t) > 0 else 'ccw'
+            direction = 'cw' if np.sign(t) > 0 else 'ccw'
 
-            startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
-            stopangle = arctan2(p3[1] - center[1], p3[0] - center[0])
+            startangle = np.arctan2(p1[1] - center[1], p1[0] - center[0])
+            stopangle = np.arctan2(p3[1] - center[1], p3[0] - center[0])
 
             self.geometry = DrawToolShape(LineString(arc(center, radius, startangle, stopangle,
                                                          direction, self.steps_per_circ)))
 
         else:  # self.mode == '12c'
-            p1 = array(self.points[0])
-            p2 = array(self.points[1])
-            pc = array(self.points[2])
+            p1 = np.array(self.points[0])
+            p2 = np.array(self.points[1])
+            pc = np.array(self.points[2])
 
             # Midpoint
             a = (p1 + p2) / 2.0
@@ -2187,7 +2181,7 @@ class FCArc(FCShapeTool):
             c = p2 - p1
 
             # Perpendicular vector
-            b = dot(c, array([[0, -1], [1, 0]], dtype=float32))
+            b = np.dot(c, np.array([[0, -1], [1, 0]], dtype=np.float32))
             b /= numpy_norm(b)
 
             # Distance
@@ -2196,14 +2190,14 @@ class FCArc(FCShapeTool):
             # Which side? Cross product with c.
             # cross(M-A, B-A), where line is AB and M is test point.
             side = (pc[0] - p1[0]) * c[1] - (pc[1] - p1[1]) * c[0]
-            t *= sign(side)
+            t *= np.sign(side)
 
             # Center = a + bt
             center = a + b * t
 
             radius = numpy_norm(center - p1)
-            startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
-            stopangle = arctan2(p2[1] - center[1], p2[0] - center[0])
+            startangle = np.arctan2(p1[1] - center[1], p1[0] - center[0])
+            stopangle = np.arctan2(p2[1] - center[1], p2[0] - center[0])
 
             self.geometry = DrawToolShape(LineString(arc(center, radius, startangle, stopangle,
                                                          self.direction, self.steps_per_circ)))
@@ -2228,7 +2222,7 @@ class FCRectangle(FCShapeTool):
         self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero.png'))
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
 
-        self.draw_app.app.inform.emit( _("Click on 1st corner ..."))
+        self.draw_app.app.inform.emit(_("Click on 1st corner ..."))
 
     def click(self, point):
         self.points.append(point)
@@ -2705,7 +2699,6 @@ class FCText(FCShapeTool):
         self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_text.png'))
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
 
-
         # self.shape_buffer = self.draw_app.shape_buffer
         self.draw_app = draw_app
         self.app = draw_app.app
@@ -2728,21 +2721,19 @@ class FCText(FCShapeTool):
                 log.debug("Font geometry is empty or incorrect: %s" % str(e))
                 self.draw_app.app.inform.emit('[ERROR] %s: %s' %
                                               (_("Font not supported. Only Regular, Bold, Italic and BoldItalic are "
-                                              "supported. Error"), str(e)))
+                                                 "supported. Error"), str(e)))
                 self.text_gui.text_path = []
                 self.text_gui.hide_tool()
                 self.draw_app.select_tool('select')
                 return
         else:
-            self.draw_app.app.inform.emit('[WARNING_NOTCL] %s' %
-                                          _("No text to add."))
+            self.draw_app.app.inform.emit('[WARNING_NOTCL] %s' % _("No text to add."))
             return
 
         self.text_gui.text_path = []
         self.text_gui.hide_tool()
         self.complete = True
-        self.draw_app.app.inform.emit('[success]%s' %
-                                      _(" Done. Adding Text completed."))
+        self.draw_app.app.inform.emit('[success]%s' % _(" Done. Adding Text completed."))
 
     def utility_geometry(self, data=None):
         """
@@ -3060,7 +3051,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
             "text": {"button": self.app.ui.geo_add_text_btn,
                      "constructor": FCText},
             "buffer": {"button": self.app.ui.geo_add_buffer_btn,
-                     "constructor": FCBuffer},
+                       "constructor": FCBuffer},
             "paint": {"button": self.app.ui.geo_add_paint_btn,
                        "constructor": FCPaint},
             "eraser": {"button": self.app.ui.geo_eraser_btn,
@@ -3068,11 +3059,11 @@ class FlatCAMGeoEditor(QtCore.QObject):
             "move": {"button": self.app.ui.geo_move_btn,
                      "constructor": FCMove},
             "transform": {"button": self.app.ui.geo_transform_btn,
-                      "constructor": FCTransform},
+                          "constructor": FCTransform},
             "copy": {"button": self.app.ui.geo_copy_btn,
                      "constructor": FCCopy},
             "explode": {"button": self.app.ui.geo_explode_btn,
-                     "constructor": FCExplode}
+                        "constructor": FCExplode}
         }
 
         # # ## Data
@@ -3502,7 +3493,6 @@ class FlatCAMGeoEditor(QtCore.QObject):
         except (TypeError, AttributeError):
             pass
 
-
         try:
             self.app.ui.draw_text.triggered.disconnect()
         except (TypeError, AttributeError):
@@ -3561,12 +3551,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
                 self.add_shape(subshape)
             return
 
-        assert isinstance(shape, DrawToolShape), \
-            "Expected a DrawToolShape, got %s" % type(shape)
-
-        assert shape.geo is not None, \
-            "Shape object has empty geometry (None)"
-
+        assert isinstance(shape, DrawToolShape), "Expected a DrawToolShape, got %s" % type(shape)
+        assert shape.geo is not None, "Shape object has empty geometry (None)"
         assert (isinstance(shape.geo, list) and len(shape.geo) > 0) or \
                not isinstance(shape.geo, list), "Shape objects has empty geometry ([])"
 
@@ -4122,13 +4108,17 @@ class FlatCAMGeoEditor(QtCore.QObject):
                 continue
 
             if shape in self.selected:
-                self.plot_shape(geometry=shape.geo, color=self.app.defaults['global_sel_draw_color'] + 'FF', linewidth=2)
+                self.plot_shape(geometry=shape.geo,
+                                color=self.app.defaults['global_sel_draw_color'] + 'FF',
+                                linewidth=2)
                 continue
 
-            self.plot_shape(geometry=shape.geo, color=self.app.defaults['global_draw_color'] + "FF")
+            self.plot_shape(geometry=shape.geo,
+                            color=self.app.defaults['global_draw_color'] + "FF")
 
         for shape in self.utility:
-            self.plot_shape(geometry=shape.geo, linewidth=1)
+            self.plot_shape(geometry=shape.geo,
+                            linewidth=1)
             continue
 
         self.shapes.redraw()
@@ -4237,7 +4227,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
         """
 
         snap_x, snap_y = (x, y)
-        snap_distance = Inf
+        snap_distance = np.Inf
 
         # # ## Object (corner?) snap
         # # ## No need for the objects, just the coordinates
@@ -4488,7 +4478,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
         results = []
         for t in selected:
             if isinstance(t.geo, Polygon) and not t.geo.is_empty:
-                results.append((t.geo.exterior).buffer(
+                results.append(t.geo.exterior.buffer(
                     buf_distance - 1e-10,
                     resolution=int(int(self.app.defaults["geometry_circle_steps"]) / 4),
                     join_style=join_style)
@@ -4519,22 +4509,18 @@ class FlatCAMGeoEditor(QtCore.QObject):
         selected = self.get_selected()
 
         if buf_distance < 0:
-            self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                 _("Negative buffer value is not accepted.")
-            )
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Negative buffer value is not accepted."))
             # deselect everything
             self.selected = []
             self.replot()
             return 'fail'
 
         if len(selected) == 0:
-            self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                 _("Nothing selected for buffering."))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Nothing selected for buffering."))
             return 'fail'
 
         if not isinstance(buf_distance, float):
-            self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                 _("Invalid distance for buffering."))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Invalid distance for buffering."))
             # deselect everything
             self.selected = []
             self.replot()
@@ -4546,7 +4532,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
                 t.geo = Polygon(t.geo)
 
             if isinstance(t.geo, Polygon) and not t.geo.is_empty:
-                results.append((t.geo).buffer(
+                results.append(t.geo.buffer(
                     -buf_distance + 1e-10,
                     resolution=int(int(self.app.defaults["geometry_circle_steps"]) / 4),
                     join_style=join_style)
@@ -4564,8 +4550,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
             self.add_shape(DrawToolShape(sha))
 
         self.replot()
-        self.app.inform.emit('[success] %s' %
-                             _("Interior buffer geometry created."))
+        self.app.inform.emit('[success] %s' % _("Interior buffer geometry created."))
 
     def buffer_ext(self, buf_distance, join_style):
         selected = self.get_selected()
@@ -4598,7 +4583,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
                 t.geo = Polygon(t.geo)
 
             if isinstance(t.geo, Polygon) and not t.geo.is_empty:
-                results.append((t.geo).buffer(
+                results.append(t.geo.buffer(
                     buf_distance,
                     resolution=int(int(self.app.defaults["geometry_circle_steps"]) / 4),
                     join_style=join_style)
@@ -4616,68 +4601,11 @@ class FlatCAMGeoEditor(QtCore.QObject):
             self.add_shape(DrawToolShape(sha))
 
         self.replot()
-        self.app.inform.emit('[success] %s' %
-                             _("Exterior buffer geometry created."))
-
-    # def paint(self, tooldia, overlap, margin, method):
-    #     selected = self.get_selected()
-    #
-    #     if len(selected) == 0:
-    #         self.app.inform.emit("[WARNING] Nothing selected for painting.")
-    #         return
-    #
-    #     for param in [tooldia, overlap, margin]:
-    #         if not isinstance(param, float):
-    #             param_name = [k for k, v in locals().items() if v is param][0]
-    #             self.app.inform.emit("[WARNING] Invalid value for {}".format(param))
-    #
-    #     # Todo: Check for valid method.
-    #
-    #     # Todo: This is the 3rd implementation on painting polys... try to consolidate
-    #
-    #     results = []
-    #
-    #     def recurse(geo):
-    #         try:
-    #             for subg in geo:
-    #                 for subsubg in recurse(subg):
-    #                     yield subsubg
-    #         except TypeError:
-    #             if isinstance(geo, LinearRing):
-    #                 yield geo
-    #
-    #         raise StopIteration
-    #
-    #     for geo in selected:
-    #         print(type(geo.geo))
-    #
-    #         local_results = []
-    #         for poly in recurse(geo.geo):
-    #             if method == "seed":
-    #                 # Type(cp) == FlatCAMRTreeStorage | None
-    #                 cp = Geometry.clear_polygon2(poly.buffer(-margin),
-    #                                              tooldia, overlap=overlap)
-    #
-    #             else:
-    #                 # Type(cp) == FlatCAMRTreeStorage | None
-    #                 cp = Geometry.clear_polygon(poly.buffer(-margin),
-    #                                             tooldia, overlap=overlap)
-    #
-    #             if cp is not None:
-    #                 local_results += list(cp.get_objects())
-    #
-    #             results.append(cascaded_union(local_results))
-    #
-    #     # This is a dirty patch:
-    #     for r in results:
-    #         self.add_shape(DrawToolShape(r))
-    #
-    #     self.replot()
+        self.app.inform.emit('[success] %s' % _("Exterior buffer geometry created."))
 
     def paint(self, tooldia, overlap, margin, connect, contour, method):
 
         self.paint_tooldia = tooldia
-
         selected = self.get_selected()
 
         if len(selected) == 0:
@@ -4814,11 +4742,11 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
 
 def distance(pt1, pt2):
-    return sqrt((pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2)
+    return np.sqrt((pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2)
 
 
 def mag(vec):
-    return sqrt(vec[0] ** 2 + vec[1] ** 2)
+    return np.sqrt(vec[0] ** 2 + vec[1] ** 2)
 
 
 def poly2rings(poly):
@@ -4826,10 +4754,10 @@ def poly2rings(poly):
 
 
 def get_shapely_list_bounds(geometry_list):
-    xmin = Inf
-    ymin = Inf
-    xmax = -Inf
-    ymax = -Inf
+    xmin = np.Inf
+    ymin = np.Inf
+    xmax = -np.Inf
+    ymax = -np.Inf
 
     for gs in geometry_list:
         try:

+ 88 - 90
flatcamEditors/FlatCAMGrbEditor.py

@@ -8,29 +8,28 @@
 from PyQt5 import QtGui, QtCore, QtWidgets
 from PyQt5.QtCore import Qt, QSettings
 
-from shapely.geometry import LineString, LinearRing, MultiLineString
-# from shapely.geometry import mapping
-from shapely.ops import cascaded_union, unary_union
+from shapely.geometry import LineString, LinearRing, MultiLineString, Point, Polygon, MultiPolygon
+from shapely.ops import cascaded_union
 import shapely.affinity as affinity
 
-from numpy import arctan2, Inf, array, sqrt, sign, dot
-from rtree import index as rtindex
 import threading
 import time
 from copy import copy, deepcopy
+import logging
 
-from camlib import *
+from camlib import distance, arc, three_point_circle
 from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, LengthEntry, RadioSet, \
-    SpinBoxDelegate, EvalEntry, EvalEntry2, FCInputDialog, FCButton, OptionalInputSection, FCCheckBox
-from FlatCAMObj import FlatCAMGerber
-from flatcamParsers.ParseGerber import Gerber
+    EvalEntry2, FCInputDialog, FCButton, OptionalInputSection, FCCheckBox
 from FlatCAMTool import FlatCAMTool
+import FlatCAMApp
 
+import numpy as np
 from numpy.linalg import norm as numpy_norm
+import math
 
 # from vispy.io import read_png
 # import pngcanvas
-
+import traceback
 import gettext
 import FlatCAMTranslation as fcTranslate
 import builtins
@@ -39,6 +38,8 @@ fcTranslate.apply_language('strings')
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
+log = logging.getLogger('base')
+
 
 class DrawToolShape(object):
     """
@@ -147,10 +148,10 @@ class DrawTool(object):
     def bounds(obj):
         def bounds_rec(o):
             if type(o) is list:
-                minx = Inf
-                miny = Inf
-                maxx = -Inf
-                maxy = -Inf
+                minx = np.Inf
+                miny = np.Inf
+                maxx = -np.Inf
+                maxy = -np.Inf
 
                 for k in o:
                     try:
@@ -311,13 +312,13 @@ class FCPad(FCShapeTool):
                 p4 = (point_x - self.half_width, point_y + self.half_height - self.half_width)
 
                 down_center = [point_x, point_y - self.half_height + self.half_width]
-                d_start_angle = math.pi
+                d_start_angle = np.pi
                 d_stop_angle = 0.0
                 down_arc = arc(down_center, self.half_width, d_start_angle, d_stop_angle, 'ccw', self.steps_per_circ)
 
                 up_center = [point_x, point_y + self.half_height - self.half_width]
                 u_start_angle = 0.0
-                u_stop_angle = math.pi
+                u_stop_angle = np.pi
                 up_arc = arc(up_center, self.half_width, u_start_angle, u_stop_angle, 'ccw', self.steps_per_circ)
 
                 geo.append(p1)
@@ -340,13 +341,13 @@ class FCPad(FCShapeTool):
                 p4 = (point_x - self.half_width + self.half_height, point_y + self.half_height)
 
                 left_center = [point_x - self.half_width + self.half_height, point_y]
-                d_start_angle = math.pi / 2
-                d_stop_angle = 1.5 * math.pi
+                d_start_angle = np.pi / 2
+                d_stop_angle = 1.5 * np.pi
                 left_arc = arc(left_center, self.half_height, d_start_angle, d_stop_angle, 'ccw', self.steps_per_circ)
 
                 right_center = [point_x + self.half_width - self.half_height, point_y]
-                u_start_angle = 1.5 * math.pi
-                u_stop_angle = math.pi / 2
+                u_start_angle = 1.5 * np.pi
+                u_stop_angle = np.pi / 2
                 right_arc = arc(right_center, self.half_height, u_start_angle, u_stop_angle, 'ccw', self.steps_per_circ)
 
                 geo.append(p1)
@@ -618,13 +619,13 @@ class FCPadArray(FCShapeTool):
                 p4 = (point_x - self.half_width, point_y + self.half_height - self.half_width)
 
                 down_center = [point_x, point_y - self.half_height + self.half_width]
-                d_start_angle = math.pi
+                d_start_angle = np.pi
                 d_stop_angle = 0.0
                 down_arc = arc(down_center, self.half_width, d_start_angle, d_stop_angle, 'ccw', self.steps_per_circ)
 
                 up_center = [point_x, point_y + self.half_height - self.half_width]
                 u_start_angle = 0.0
-                u_stop_angle = math.pi
+                u_stop_angle = np.pi
                 up_arc = arc(up_center, self.half_width, u_start_angle, u_stop_angle, 'ccw', self.steps_per_circ)
 
                 geo.append(p1)
@@ -647,13 +648,13 @@ class FCPadArray(FCShapeTool):
                 p4 = (point_x - self.half_width + self.half_height, point_y + self.half_height)
 
                 left_center = [point_x - self.half_width + self.half_height, point_y]
-                d_start_angle = math.pi / 2
-                d_stop_angle = 1.5 * math.pi
+                d_start_angle = np.pi / 2
+                d_stop_angle = 1.5 * np.pi
                 left_arc = arc(left_center, self.half_height, d_start_angle, d_stop_angle, 'ccw', self.steps_per_circ)
 
                 right_center = [point_x + self.half_width - self.half_height, point_y]
-                u_start_angle = 1.5 * math.pi
-                u_stop_angle = math.pi / 2
+                u_start_angle = 1.5 * np.pi
+                u_stop_angle = np.pi / 2
                 right_arc = arc(right_center, self.half_height, u_start_angle, u_stop_angle, 'ccw', self.steps_per_circ)
 
                 geo.append(p1)
@@ -1296,7 +1297,7 @@ class FCTrack(FCRegion):
                 self.draw_app.bend_mode = 2
                 self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_path2.png'))
                 QtGui.QGuiApplication.setOverrideCursor(self.cursor)
-                msg =  _('Track Mode 2: Reverse 45 degrees ...')
+                msg = _('Track Mode 2: Reverse 45 degrees ...')
             elif self.draw_app.bend_mode == 2:
                 self.draw_app.bend_mode = 3
                 self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_path3.png'))
@@ -1415,7 +1416,7 @@ class FCDisc(FCShapeTool):
         if len(self.points) == 1:
             p1 = self.points[0]
             p2 = data
-            radius = sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)
+            radius = math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)
             new_geo_el['solid'] = Point(p1).buffer((radius + self.buf_val / 2), int(self.steps_per_circ / 4))
             return DrawToolUtilityShape(new_geo_el)
 
@@ -1557,9 +1558,9 @@ class FCSemiDisc(FCShapeTool):
                 p1 = self.points[1]
                 p2 = data
 
-                radius = sqrt((center[0] - p1[0]) ** 2 + (center[1] - p1[1]) ** 2) + (self.buf_val / 2)
-                startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
-                stopangle = arctan2(p2[1] - center[1], p2[0] - center[0])
+                radius = np.sqrt((center[0] - p1[0]) ** 2 + (center[1] - p1[1]) ** 2) + (self.buf_val / 2)
+                startangle = np.arctan2(p1[1] - center[1], p1[0] - center[0])
+                stopangle = np.arctan2(p2[1] - center[1], p2[0] - center[0])
 
                 new_geo_el['solid'] = LineString(
                     arc(center, radius, startangle, stopangle, self.direction, self.steps_per_circ))
@@ -1567,20 +1568,20 @@ class FCSemiDisc(FCShapeTool):
                 return DrawToolUtilityShape([new_geo_el, new_geo_el_pt1])
 
             elif self.mode == '132':
-                p1 = array(self.points[0])
-                p3 = array(self.points[1])
-                p2 = array(data)
+                p1 = np.array(self.points[0])
+                p3 = np.array(self.points[1])
+                p2 = np.array(data)
 
                 try:
                     center, radius, t = three_point_circle(p1, p2, p3)
                 except TypeError:
                     return
 
-                direction = 'cw' if sign(t) > 0 else 'ccw'
+                direction = 'cw' if np.sign(t) > 0 else 'ccw'
                 radius += (self.buf_val / 2)
 
-                startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
-                stopangle = arctan2(p3[1] - center[1], p3[0] - center[0])
+                startangle = np.arctan2(p1[1] - center[1], p1[0] - center[0])
+                stopangle = np.arctan2(p3[1] - center[1], p3[0] - center[0])
 
                 new_geo_el['solid'] = LineString(
                     arc(center, radius, startangle, stopangle, direction, self.steps_per_circ))
@@ -1591,8 +1592,8 @@ class FCSemiDisc(FCShapeTool):
                 return DrawToolUtilityShape([new_geo_el, new_geo_el_pt2, new_geo_el_pt1, new_geo_el_pt3])
 
             else:  # '12c'
-                p1 = array(self.points[0])
-                p2 = array(self.points[1])
+                p1 = np.array(self.points[0])
+                p2 = np.array(self.points[1])
                 # Midpoint
                 a = (p1 + p2) / 2.0
 
@@ -1600,7 +1601,7 @@ class FCSemiDisc(FCShapeTool):
                 c = p2 - p1
 
                 # Perpendicular vector
-                b = dot(c, array([[0, -1], [1, 0]], dtype=float32))
+                b = np.dot(c, np.array([[0, -1], [1, 0]], dtype=np.float32))
                 b /= numpy_norm(b)
 
                 # Distance
@@ -1609,14 +1610,14 @@ class FCSemiDisc(FCShapeTool):
                 # Which side? Cross product with c.
                 # cross(M-A, B-A), where line is AB and M is test point.
                 side = (data[0] - p1[0]) * c[1] - (data[1] - p1[1]) * c[0]
-                t *= sign(side)
+                t *= np.sign(side)
 
                 # Center = a + bt
                 center = a + b * t
 
                 radius = numpy_norm(center - p1) + (self.buf_val / 2)
-                startangle = arctan2(p1[1] - center[1], p1[0] - center[0])
-                stopangle = arctan2(p2[1] - center[1], p2[0] - center[0])
+                startangle = np.arctan2(p1[1] - center[1], p1[0] - center[0])
+                stopangle = np.arctan2(p2[1] - center[1], p2[0] - center[0])
 
                 new_geo_el['solid'] = LineString(
                     arc(center, radius, startangle, stopangle, self.direction, self.steps_per_circ))
@@ -1636,8 +1637,8 @@ class FCSemiDisc(FCShapeTool):
             p2 = self.points[2]
 
             radius = distance(center, p1) + (self.buf_val / 2)
-            start_angle = arctan2(p1[1] - center[1], p1[0] - center[0])
-            stop_angle = arctan2(p2[1] - center[1], p2[0] - center[0])
+            start_angle = np.arctan2(p1[1] - center[1], p1[0] - center[0])
+            stop_angle = np.arctan2(p2[1] - center[1], p2[0] - center[0])
             new_geo_el['solid'] = Polygon(
                 arc(center, radius, start_angle, stop_angle, self.direction, self.steps_per_circ))
             new_geo_el['follow'] = Polygon(
@@ -1645,16 +1646,16 @@ class FCSemiDisc(FCShapeTool):
             self.geometry = DrawToolShape(new_geo_el)
 
         elif self.mode == '132':
-            p1 = array(self.points[0])
-            p3 = array(self.points[1])
-            p2 = array(self.points[2])
+            p1 = np.array(self.points[0])
+            p3 = np.array(self.points[1])
+            p2 = np.array(self.points[2])
 
             center, radius, t = three_point_circle(p1, p2, p3)
-            direction = 'cw' if sign(t) > 0 else 'ccw'
+            direction = 'cw' if np.sign(t) > 0 else 'ccw'
             radius += (self.buf_val / 2)
 
-            start_angle = arctan2(p1[1] - center[1], p1[0] - center[0])
-            stop_angle = arctan2(p3[1] - center[1], p3[0] - center[0])
+            start_angle = np.arctan2(p1[1] - center[1], p1[0] - center[0])
+            stop_angle = np.arctan2(p3[1] - center[1], p3[0] - center[0])
 
             new_geo_el['solid'] = Polygon(arc(center, radius, start_angle, stop_angle, direction, self.steps_per_circ))
             new_geo_el['follow'] = Polygon(
@@ -1662,9 +1663,9 @@ class FCSemiDisc(FCShapeTool):
             self.geometry = DrawToolShape(new_geo_el)
 
         else:  # self.mode == '12c'
-            p1 = array(self.points[0])
-            p2 = array(self.points[1])
-            pc = array(self.points[2])
+            p1 = np.array(self.points[0])
+            p2 = np.array(self.points[1])
+            pc = np.array(self.points[2])
 
             # Midpoint
             a = (p1 + p2) / 2.0
@@ -1673,7 +1674,7 @@ class FCSemiDisc(FCShapeTool):
             c = p2 - p1
 
             # Perpendicular vector
-            b = dot(c, array([[0, -1], [1, 0]], dtype=float32))
+            b = np.dot(c, np.array([[0, -1], [1, 0]], dtype=np.float32))
             b /= numpy_norm(b)
 
             # Distance
@@ -1682,14 +1683,14 @@ class FCSemiDisc(FCShapeTool):
             # Which side? Cross product with c.
             # cross(M-A, B-A), where line is AB and M is test point.
             side = (pc[0] - p1[0]) * c[1] - (pc[1] - p1[1]) * c[0]
-            t *= sign(side)
+            t *= np.sign(side)
 
             # Center = a + bt
             center = a + b * t
 
             radius = numpy_norm(center - p1) + (self.buf_val / 2)
-            start_angle = arctan2(p1[1] - center[1], p1[0] - center[0])
-            stop_angle = arctan2(p2[1] - center[1], p2[0] - center[0])
+            start_angle = np.arctan2(p1[1] - center[1], p1[0] - center[0])
+            stop_angle = np.arctan2(p2[1] - center[1], p2[0] - center[0])
 
             new_geo_el['solid'] = Polygon(
                 arc(center, radius, start_angle, stop_angle, self.direction, self.steps_per_circ))
@@ -2437,9 +2438,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.apertures_box.addLayout(grid1)
 
         apcode_lbl = QtWidgets.QLabel('%s:' % _('Aperture Code'))
-        apcode_lbl.setToolTip(
-        _("Code for the new aperture")
-        )
+        apcode_lbl.setToolTip(_("Code for the new aperture"))
         grid1.addWidget(apcode_lbl, 1, 0)
 
         self.apcode_entry = FCEntry()
@@ -2448,11 +2447,11 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         apsize_lbl = QtWidgets.QLabel('%s:' % _('Aperture Size'))
         apsize_lbl.setToolTip(
-        _("Size for the new aperture.\n"
-          "If aperture type is 'R' or 'O' then\n"
-          "this value is automatically\n"
-          "calculated as:\n"
-          "sqrt(width**2 + height**2)")
+            _("Size for the new aperture.\n"
+              "If aperture type is 'R' or 'O' then\n"
+              "this value is automatically\n"
+              "calculated as:\n"
+              "sqrt(width**2 + height**2)")
         )
         grid1.addWidget(apsize_lbl, 2, 0)
 
@@ -2462,10 +2461,10 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         aptype_lbl = QtWidgets.QLabel('%s:' % _('Aperture Type'))
         aptype_lbl.setToolTip(
-        _("Select the type of new aperture. Can be:\n"
-          "C = circular\n"
-          "R = rectangular\n"
-          "O = oblong")
+            _("Select the type of new aperture. Can be:\n"
+              "C = circular\n"
+              "R = rectangular\n"
+              "O = oblong")
         )
         grid1.addWidget(aptype_lbl, 3, 0)
 
@@ -2475,9 +2474,9 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         self.apdim_lbl = QtWidgets.QLabel('%s:' % _('Aperture Dim'))
         self.apdim_lbl.setToolTip(
-        _("Dimensions for the new aperture.\n"
-          "Active only for rectangular apertures (type R).\n"
-          "The format is (width, height)")
+            _("Dimensions for the new aperture.\n"
+              "Active only for rectangular apertures (type R).\n"
+              "The format is (width, height)")
         )
         grid1.addWidget(self.apdim_lbl, 4, 0)
 
@@ -2495,12 +2494,12 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         self.addaperture_btn = QtWidgets.QPushButton(_('Add'))
         self.addaperture_btn.setToolTip(
-           _( "Add a new aperture to the aperture list.")
+           _("Add a new aperture to the aperture list.")
         )
 
         self.delaperture_btn = QtWidgets.QPushButton(_('Delete'))
         self.delaperture_btn.setToolTip(
-           _( "Delete a aperture in the aperture list")
+           _("Delete a aperture in the aperture list")
         )
         hlay_ad.addWidget(self.addaperture_btn)
         hlay_ad.addWidget(self.delaperture_btn)
@@ -2677,8 +2676,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         self.array_type_combo = FCComboBox()
         self.array_type_combo.setToolTip(
-           _( "Select the type of pads array to create.\n"
-              "It can be Linear X(Y) or Circular")
+           _("Select the type of pads array to create.\n"
+             "It can be Linear X(Y) or Circular")
         )
         self.array_type_combo.addItem(_("Linear"))
         self.array_type_combo.addItem(_("Circular"))
@@ -2733,10 +2732,10 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         self.linear_angle_label = QtWidgets.QLabel('%s:' % _('Angle'))
         self.linear_angle_label.setToolTip(
-           _( "Angle at which the linear array is placed.\n"
-              "The precision is of max 2 decimals.\n"
-              "Min value is: -359.99 degrees.\n"
-              "Max value is:  360.00 degrees.")
+           _("Angle at which the linear array is placed.\n"
+             "The precision is of max 2 decimals.\n"
+             "Min value is: -359.99 degrees.\n"
+             "Max value is:  360.00 degrees.")
         )
         self.linear_angle_label.setMinimumWidth(100)
 
@@ -2808,9 +2807,9 @@ class FlatCAMGrbEditor(QtCore.QObject):
             "scale": {"button": self.app.ui.aperture_scale_btn,
                       "constructor": FCScale},
             "markarea": {"button": self.app.ui.aperture_markarea_btn,
-                      "constructor": FCMarkArea},
+                         "constructor": FCMarkArea},
             "eraser": {"button": self.app.ui.aperture_eraser_btn,
-                      "constructor": FCEraser},
+                       "constructor": FCEraser},
             "copy": {"button": self.app.ui.aperture_copy_btn,
                      "constructor": FCApertureCopy},
             "transform": {"button": self.app.ui.grb_transform_btn,
@@ -3245,7 +3244,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
                         self.storage_dict[ap_id]['width'] = dims[0]
                         self.storage_dict[ap_id]['height'] = dims[1]
 
-                        size_val = math.sqrt((dims[0] ** 2) + (dims[1] ** 2))
+                        size_val = np.sqrt((dims[0] ** 2) + (dims[1] ** 2))
                         self.apsize_entry.set_value(size_val)
 
                     except Exception as e:
@@ -3613,7 +3612,6 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.app.ui.grb_draw_eraser.triggered.connect(self.on_eraser)
         self.app.ui.grb_draw_transformations.triggered.connect(self.on_transform)
 
-
     def disconnect_canvas_event_handlers(self):
 
         # we restore the key and mouse control to FlatCAMApp method
@@ -3803,7 +3801,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         # we subtract the big "negative" (clear) geometry from each solid polygon but only the part of clear geometry
         # that fits inside the solid. otherwise we may loose the solid
         for apid in self.gerber_obj.apertures:
-            temp_solid_geometry= []
+            temp_solid_geometry = []
             if 'geometry' in self.gerber_obj.apertures[apid]:
                 # for elem in self.gerber_obj.apertures[apid]['geometry']:
                 #     if 'solid' in elem:
@@ -6032,10 +6030,10 @@ class TransformEditorTool(FlatCAMTool):
 
 
 def get_shapely_list_bounds(geometry_list):
-    xmin = Inf
-    ymin = Inf
-    xmax = -Inf
-    ymax = -Inf
+    xmin = np.Inf
+    ymin = np.Inf
+    xmax = -np.Inf
+    ymax = -np.Inf
 
     for gs in geometry_list:
         try:

+ 39 - 26
flatcamGUI/FlatCAMGUI.py

@@ -12,8 +12,12 @@
 # ##########################################################
 
 from flatcamGUI.PreferencesUI import *
+from flatcamEditors.FlatCAMGeoEditor import FCShapeTool
 from matplotlib.backend_bases import KeyEvent as mpl_key_event
 
+import webbrowser
+from copy import deepcopy
+from datetime import datetime
 import gettext
 import FlatCAMTranslation as fcTranslate
 import builtins
@@ -70,15 +74,15 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         self.menufilenew = self.menufile.addMenu(QtGui.QIcon('share/file16.png'), _('&New'))
         self.menufilenew.setToolTipsVisible(True)
 
-        self.menufilenewgeo = self.menufilenew.addAction(QtGui.QIcon('share/geometry16.png'), _('Geometry\tN'))
+        self.menufilenewgeo = self.menufilenew.addAction(QtGui.QIcon('share/new_file_geo16.png'), _('Geometry\tN'))
         self.menufilenewgeo.setToolTip(
             _("Will create a new, empty Geometry Object.")
         )
-        self.menufilenewgrb = self.menufilenew.addAction(QtGui.QIcon('share/flatcam_icon32.png'), _('Gerber\tB'))
+        self.menufilenewgrb = self.menufilenew.addAction(QtGui.QIcon('share/new_file_grb16.png'), _('Gerber\tB'))
         self.menufilenewgrb.setToolTip(
             _("Will create a new, empty Gerber Object.")
         )
-        self.menufilenewexc = self.menufilenew.addAction(QtGui.QIcon('share/drill16.png'), _('Excellon\tL'))
+        self.menufilenewexc = self.menufilenew.addAction(QtGui.QIcon('share/new_file_exc16.png'), _('Excellon\tL'))
         self.menufilenewexc.setToolTip(
             _("Will create a new, empty Excellon Object.")
         )
@@ -416,6 +420,12 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         # ########################## Objects # ###################################
         # ########################################################################
         self.menuobjects = self.menu.addMenu(_('Objects'))
+        self.menuobjects.addSeparator()
+        self.menuobjects_selall = self.menuobjects.addAction(QtGui.QIcon('share/select_all.png'), _('Select All'))
+        self.menuobjects_unselall = self.menuobjects.addAction(
+            QtGui.QIcon('share/deselect_all32.png'),
+            _('Deselect All')
+        )
 
         # ########################################################################
         # ########################## Tool # ######################################
@@ -682,22 +692,23 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         self.file_open_excellon_btn = self.toolbarfile.addAction(QtGui.QIcon('share/drill32.png'), _("Open Excellon"))
         self.toolbarfile.addSeparator()
         self.file_open_btn = self.toolbarfile.addAction(QtGui.QIcon('share/folder32.png'), _("Open project"))
-        self.file_save_btn = self.toolbarfile.addAction(QtGui.QIcon('share/floppy32.png'), _("Save project"))
+        self.file_save_btn = self.toolbarfile.addAction(QtGui.QIcon('share/project_save32.png'), _("Save project"))
 
         # ########################################################################
         # ########################## Edit Toolbar# ###############################
         # ########################################################################
-        self.newgeo_btn = self.toolbargeo.addAction(QtGui.QIcon('share/new_geo32_bis.png'), _("New Blank Geometry"))
-        self.newgrb_btn = self.toolbargeo.addAction(QtGui.QIcon('share/new_geo32.png'), _("New Blank Gerber"))
-        self.newexc_btn = self.toolbargeo.addAction(QtGui.QIcon('share/new_exc32.png'), _("New Blank Excellon"))
+        self.newgeo_btn = self.toolbargeo.addAction(QtGui.QIcon('share/new_file_geo32.png'), _("New Blank Geometry"))
+        self.newgrb_btn = self.toolbargeo.addAction(QtGui.QIcon('share/new_file_grb32.png'), _("New Blank Gerber"))
+        self.newexc_btn = self.toolbargeo.addAction(QtGui.QIcon('share/new_file_exc32.png'), _("New Blank Excellon"))
         self.toolbargeo.addSeparator()
-        self.editgeo_btn = self.toolbargeo.addAction(QtGui.QIcon('share/edit32.png'), _("Editor"))
+        self.editgeo_btn = self.toolbargeo.addAction(QtGui.QIcon('share/edit_file32.png'), _("Editor"))
         self.update_obj_btn = self.toolbargeo.addAction(
-            QtGui.QIcon('share/edit_ok32_bis.png'), _("Save Object and close the Editor")
+            QtGui.QIcon('share/close_edit_file32.png'), _("Save Object and close the Editor")
         )
 
         self.toolbargeo.addSeparator()
-        self.delete_btn = self.toolbargeo.addAction(QtGui.QIcon('share/cancel_edit32.png'), _("&Delete"))
+        self.copy_btn = self.toolbargeo.addAction(QtGui.QIcon('share/copy_file32.png'), _("Copy"))
+        self.delete_btn = self.toolbargeo.addAction(QtGui.QIcon('share/delete_file32.png'), _("&Delete"))
         self.toolbargeo.addSeparator()
         self.distance_btn = self.toolbargeo.addAction(QtGui.QIcon('share/distance32.png'), _("Distance Tool"))
         self.distance_min_btn = self.toolbargeo.addAction(QtGui.QIcon('share/distance_min32.png'),
@@ -705,7 +716,6 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         self.origin_btn = self.toolbargeo.addAction(QtGui.QIcon('share/origin32.png'), _('Set Origin'))
         self.jmp_btn = self.toolbargeo.addAction(QtGui.QIcon('share/jump_to16.png'), _('Jump to Location'))
 
-
         # ########################################################################
         # ########################## View Toolbar# ###############################
         # ########################################################################
@@ -734,10 +744,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         self.paint_btn = self.toolbartools.addAction(QtGui.QIcon('share/paint20_1.png'), _("Paint Tool"))
         self.toolbartools.addSeparator()
 
-        self.panelize_btn = self.toolbartools.addAction(QtGui.QIcon('share/panel16.png'), _("Panel Tool"))
+        self.panelize_btn = self.toolbartools.addAction(QtGui.QIcon('share/panelize32.png'), _("Panel Tool"))
         self.film_btn = self.toolbartools.addAction(QtGui.QIcon('share/film16.png'), _("Film Tool"))
         self.solder_btn = self.toolbartools.addAction(QtGui.QIcon('share/solderpastebis32.png'), _("SolderPaste Tool"))
-        self.sub_btn = self.toolbartools.addAction(QtGui.QIcon('share/sub32.png'), _("Substract Tool"))
+        self.sub_btn = self.toolbartools.addAction(QtGui.QIcon('share/sub32.png'), _("Subtract Tool"))
         self.rules_btn = self.toolbartools.addAction(QtGui.QIcon('share/rules32.png'), _("Rules Tool"))
         self.optimal_btn = self.toolbartools.addAction(QtGui.QIcon('share/open_excellon32.png'), _("Optimal Tool"))
 
@@ -1869,7 +1879,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
         self.draw_union = self.g_editor_cmenu.addAction(QtGui.QIcon('share/union32.png'), _("Union"))
         self.draw_intersect = self.g_editor_cmenu.addAction(QtGui.QIcon('share/intersection32.png'), _("Intersection"))
-        self.draw_substract = self.g_editor_cmenu.addAction(QtGui.QIcon('share/subtract32.png'), _("Substraction"))
+        self.draw_substract = self.g_editor_cmenu.addAction(QtGui.QIcon('share/subtract32.png'), _("Subtraction"))
         self.draw_cut = self.g_editor_cmenu.addAction(QtGui.QIcon('share/cutpath32.png'), _("Cut"))
         self.draw_transform = self.g_editor_cmenu.addAction(QtGui.QIcon('share/transform.png'), _("Transformations"))
 
@@ -2106,19 +2116,21 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         self.file_open_excellon_btn = self.toolbarfile.addAction(QtGui.QIcon('share/drill32.png'), _("Open Excellon"))
         self.toolbarfile.addSeparator()
         self.file_open_btn = self.toolbarfile.addAction(QtGui.QIcon('share/folder32.png'), _("Open project"))
-        self.file_save_btn = self.toolbarfile.addAction(QtGui.QIcon('share/floppy32.png'), _("Save project"))
+        self.file_save_btn = self.toolbarfile.addAction(QtGui.QIcon('share/project_save32.png'), _("Save project"))
 
         # ## Edit Toolbar # ##
-        self.newgeo_btn = self.toolbargeo.addAction(QtGui.QIcon('share/new_geo32_bis.png'), _("New Blank Geometry"))
-        self.newexc_btn = self.toolbargeo.addAction(QtGui.QIcon('share/new_exc32.png'), _("New Blank Excellon"))
+        self.newgeo_btn = self.toolbargeo.addAction(QtGui.QIcon('share/new_file_geo32.png'), _("New Blank Geometry"))
+        self.newgrb_btn = self.toolbargeo.addAction(QtGui.QIcon('share/new_file_grb32.png'), _("New Blank Gerber"))
+        self.newexc_btn = self.toolbargeo.addAction(QtGui.QIcon('share/new_file_exc32.png'), _("New Blank Excellon"))
         self.toolbargeo.addSeparator()
         self.editgeo_btn = self.toolbargeo.addAction(QtGui.QIcon('share/edit32.png'), _("Editor"))
         self.update_obj_btn = self.toolbargeo.addAction(
-            QtGui.QIcon('share/edit_ok32_bis.png'), _("Save Object and close the Editor")
+            QtGui.QIcon('share/close_edit_file32.png'), _("Save Object and close the Editor")
         )
 
         self.toolbargeo.addSeparator()
-        self.delete_btn = self.toolbargeo.addAction(QtGui.QIcon('share/cancel_edit32.png'), _("&Delete"))
+        self.copy_btn = self.toolbargeo.addAction(QtGui.QIcon('share/copy_file32.png'), _("Copy"))
+        self.delete_btn = self.toolbargeo.addAction(QtGui.QIcon('share/delete_file32.png'), _("&Delete"))
         self.toolbargeo.addSeparator()
         self.distance_btn = self.toolbargeo.addAction(QtGui.QIcon('share/distance32.png'), _("Distance Tool"))
         self.distance_min_btn = self.toolbargeo.addAction(QtGui.QIcon('share/distance_min32.png'),
@@ -2148,11 +2160,11 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         self.paint_btn = self.toolbartools.addAction(QtGui.QIcon('share/paint20_1.png'), _("Paint Tool"))
         self.toolbartools.addSeparator()
 
-        self.panelize_btn = self.toolbartools.addAction(QtGui.QIcon('share/panel16.png'), _("Panel Tool"))
+        self.panelize_btn = self.toolbartools.addAction(QtGui.QIcon('share/panelize32.png'), _("Panel Tool"))
         self.film_btn = self.toolbartools.addAction(QtGui.QIcon('share/film16.png'), _("Film Tool"))
         self.solder_btn = self.toolbartools.addAction(QtGui.QIcon('share/solderpastebis32.png'),
                                                       _("SolderPaste Tool"))
-        self.sub_btn = self.toolbartools.addAction(QtGui.QIcon('share/sub32.png'), _("Substract Tool"))
+        self.sub_btn = self.toolbartools.addAction(QtGui.QIcon('share/sub32.png'), _("Subtract Tool"))
 
         self.toolbartools.addSeparator()
 
@@ -3696,7 +3708,7 @@ class FlatCAMSystemTray(QtWidgets.QSystemTrayIcon):
 
 class BookmarkManager(QtWidgets.QWidget):
 
-    mark_rows = pyqtSignal()
+    mark_rows = QtCore.pyqtSignal()
 
     def __init__(self, app, storage, parent=None):
         super(BookmarkManager, self).__init__(parent)
@@ -3754,7 +3766,7 @@ class BookmarkManager(QtWidgets.QWidget):
         new_vlay = QtWidgets.QVBoxLayout()
         layout.addLayout(new_vlay)
 
-        new_title_lbl = QtWidgets.QLabel(_("<b>New Bookmark</b>"))
+        new_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("New Bookmark"))
         new_vlay.addWidget(new_title_lbl)
 
         form0 = QtWidgets.QFormLayout()
@@ -3958,8 +3970,9 @@ class BookmarkManager(QtWidgets.QWidget):
 
         filter__ = "Text File (*.TXT);;All Files (*.*)"
         filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export FlatCAM Preferences"),
-                                                             directory=_('{l_save}/FlatCAM_Bookmarks_{date}').format(
+                                                             directory='{l_save}/FlatCAM_{n}_{date}'.format(
                                                                  l_save=str(self.app.get_last_save_folder()),
+                                                                 n=_("Bookmarks"),
                                                                  date=date),
                                                              filter=filter__)
 
@@ -3986,7 +3999,7 @@ class BookmarkManager(QtWidgets.QWidget):
                 self.app.log.error("Could not load defaults file.")
                 self.app.log.error(str(e))
                 self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                     _("Could not load bookamrks file."))
+                                     _("Could not load bookmarks file."))
                 return
 
             # Save update options
@@ -4019,7 +4032,7 @@ class BookmarkManager(QtWidgets.QWidget):
                 with open(filename) as f:
                     bookmarks = f.readlines()
             except IOError:
-                self.app.log.error("Could not load bookamrks file.")
+                self.app.log.error("Could not load bookmarks file.")
                 self.app.inform.emit('[ERROR_NOTCL] %s' %
                                      _("Could not load bookmarks file."))
                 return

+ 20 - 26
flatcamGUI/GUIElements.py

@@ -12,18 +12,14 @@
 # ##########################################################
 
 from PyQt5 import QtGui, QtCore, QtWidgets
-from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
+from PyQt5.QtCore import Qt, pyqtSlot
 from PyQt5.QtWidgets import QTextEdit, QCompleter, QAction
-from PyQt5.QtGui import QColor, QKeySequence, QPalette, QTextCursor
+from PyQt5.QtGui import QKeySequence, QTextCursor
 
 from copy import copy
 import re
 import logging
 import html
-import webbrowser
-from copy import deepcopy
-import sys
-from datetime import datetime
 
 log = logging.getLogger('base')
 
@@ -513,7 +509,7 @@ class EvalEntry2(QtWidgets.QLineEdit):
 
 class FCSpinner(QtWidgets.QSpinBox):
 
-    returnPressed = pyqtSignal()
+    returnPressed = QtCore.pyqtSignal()
 
     def __init__(self, parent=None):
         super(FCSpinner, self).__init__(parent)
@@ -580,7 +576,7 @@ class FCSpinner(QtWidgets.QSpinBox):
 
 class FCDoubleSpinner(QtWidgets.QDoubleSpinBox):
 
-    returnPressed = pyqtSignal()
+    returnPressed = QtCore.pyqtSignal()
 
     def __init__(self, parent=None):
         super(FCDoubleSpinner, self).__init__(parent)
@@ -869,16 +865,16 @@ class FCTextAreaExtended(QtWidgets.QTextEdit):
         if self.textCursor().block().text().startswith(" "):
             # skip the white space
             self.moveCursor(QtGui.QTextCursor.NextWord)
-        self.moveCursor(QtGui.QTextCursor.NextCharacter,QtGui.QTextCursor.KeepAnchor)
+        self.moveCursor(QtGui.QTextCursor.NextCharacter, QtGui.QTextCursor.KeepAnchor)
         character = self.textCursor().selectedText()
         if character == "#":
             # delete #
             self.textCursor().deletePreviousChar()
             # delete white space 
-            self.moveCursor(QtGui.QTextCursor.NextWord,QtGui.QTextCursor.KeepAnchor)
+            self.moveCursor(QtGui.QTextCursor.NextWord, QtGui.QTextCursor.KeepAnchor)
             self.textCursor().removeSelectedText()
         else:
-            self.moveCursor(QtGui.QTextCursor.PreviousCharacter,QtGui.QTextCursor.KeepAnchor)
+            self.moveCursor(QtGui.QTextCursor.PreviousCharacter, QtGui.QTextCursor.KeepAnchor)
             self.textCursor().insertText("# ")
         cursor = QtGui.QTextCursor(self.textCursor())
         cursor.setPosition(pos)
@@ -1261,7 +1257,6 @@ class FCDetachableTab(QtWidgets.QTabWidget):
                 attached = True
                 break
 
-
         # If the tab is not attached, close it's window and
         # remove the reference to it
         if not attached:
@@ -1342,8 +1337,8 @@ class FCDetachableTab(QtWidgets.QTabWidget):
         can be re-attached by closing the dialog or by dragging the window into the tab bar
         """
 
-        onCloseSignal = pyqtSignal(QtWidgets.QWidget, str, QtGui.QIcon)
-        onDropSignal = pyqtSignal(str, QtCore.QPoint)
+        onCloseSignal = QtCore.pyqtSignal(QtWidgets.QWidget, str, QtGui.QIcon)
+        onDropSignal = QtCore.pyqtSignal(str, QtCore.QPoint)
 
         def __init__(self, name, contentWidget):
             QtWidgets.QMainWindow.__init__(self, None)
@@ -1384,7 +1379,7 @@ class FCDetachableTab(QtWidgets.QTabWidget):
             An event filter class to detect a QMainWindow drop event
             """
 
-            onDropSignal = pyqtSignal(QtCore.QPoint)
+            onDropSignal = QtCore.pyqtSignal(QtCore.QPoint)
 
             def __init__(self):
                 QtCore.QObject.__init__(self)
@@ -1416,11 +1411,11 @@ class FCDetachableTab(QtWidgets.QTabWidget):
                     return False
 
     class FCTabBar(QtWidgets.QTabBar):
-        onDetachTabSignal = pyqtSignal(int, QtCore.QPoint)
-        onMoveTabSignal = pyqtSignal(int, int)
-        detachedTabDropSignal = pyqtSignal(str, int, QtCore.QPoint)
+        onDetachTabSignal = QtCore.pyqtSignal(int, QtCore.QPoint)
+        onMoveTabSignal = QtCore.pyqtSignal(int, int)
+        detachedTabDropSignal = QtCore.pyqtSignal(str, int, QtCore.QPoint)
 
-        right_click = pyqtSignal(int)
+        right_click = QtCore.pyqtSignal(int)
 
         def __init__(self, parent=None):
             QtWidgets.QTabBar.__init__(self, parent)
@@ -1498,7 +1493,7 @@ class FCDetachableTab(QtWidgets.QTabWidget):
                 self.dragInitiated = True
 
             # If the current movement is a drag initiated by the left button
-            if (((event.buttons() & QtCore.Qt.LeftButton)) and self.dragInitiated and self.can_be_dragged):
+            if ((event.buttons() & QtCore.Qt.LeftButton)) and self.dragInitiated and self.can_be_dragged:
 
                 # Stop the move event
                 finishMoveEvent = QtGui.QMouseEvent(
@@ -1591,7 +1586,7 @@ class FCDetachableTab(QtWidgets.QTabWidget):
 
 
 class FCDetachableTab2(FCDetachableTab):
-    tab_closed_signal = pyqtSignal(object)
+    tab_closed_signal = QtCore.pyqtSignal(object)
 
     def __init__(self, protect=None, protect_by_name=None, parent=None):
         super(FCDetachableTab2, self).__init__(protect=protect, protect_by_name=protect_by_name, parent=parent)
@@ -1729,7 +1724,7 @@ class OptionalHideInputSection:
 
 class FCTable(QtWidgets.QTableWidget):
 
-    drag_drop_sig = pyqtSignal()
+    drag_drop_sig = QtCore.pyqtSignal()
 
     def __init__(self, drag_drop=False, protected_rows=None, parent=None):
         super(FCTable, self).__init__(parent)
@@ -2024,8 +2019,8 @@ class _ExpandableTextEdit(QTextEdit):
     Class implements edit line, which expands themselves automatically
     """
 
-    historyNext = pyqtSignal()
-    historyPrev = pyqtSignal()
+    historyNext = QtCore.pyqtSignal()
+    historyPrev = QtCore.pyqtSignal()
 
     def __init__(self, termwidget, *args):
         QTextEdit.__init__(self, *args)
@@ -2148,7 +2143,7 @@ class _ExpandableTextEdit(QTextEdit):
 
 
 class MyCompleter(QCompleter):
-    insertText = pyqtSignal(str)
+    insertText = QtCore.pyqtSignal(str)
 
     def __init__(self, parent=None):
         QCompleter.__init__(self)
@@ -2166,4 +2161,3 @@ class MyCompleter(QCompleter):
 
     def getSelected(self):
         return self.lastSelected
-

+ 0 - 2
flatcamGUI/ObjectUI.py

@@ -11,8 +11,6 @@
 # Date: 3/10/2019                                          #
 # ##########################################################
 
-from PyQt5 import QtGui, QtCore, QtWidgets
-from PyQt5.QtCore import Qt
 from flatcamGUI.GUIElements import *
 import sys
 

+ 1 - 1
flatcamGUI/PlotCanvasLegacy.py

@@ -7,7 +7,7 @@
 # Modified by Marius Stanciu 09/21/2019                    #
 ############################################################
 
-from PyQt5 import QtGui, QtCore, QtWidgets
+from PyQt5 import QtCore
 from PyQt5.QtCore import pyqtSignal
 
 # needed for legacy mode

+ 1 - 4
flatcamGUI/PreferencesUI.py

@@ -8,11 +8,8 @@
 from PyQt5.QtCore import QSettings
 from flatcamGUI.GUIElements import *
 import platform
-import webbrowser
 import sys
 
-from flatcamEditors.FlatCAMGeoEditor import FCShapeTool
-
 import gettext
 import FlatCAMTranslation as fcTranslate
 import builtins
@@ -5121,7 +5118,7 @@ class Tools2RulesCheckPrefGroupUI(OptionsGroupUI):
 
         self.crlabel = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
         self.crlabel.setToolTip(
-            _("A tool to check if Gerber files fir within a set\n"
+            _("A tool to check if Gerber files are within a set\n"
               "of Manufacturing Rules.")
         )
         self.layout.addWidget(self.crlabel)

+ 4 - 1
flatcamGUI/VisPyCanvas.py

@@ -6,12 +6,15 @@
 # MIT Licence                                              #
 # ##########################################################
 
-import numpy as np
 from PyQt5.QtGui import QPalette
 from PyQt5.QtCore import QSettings
+
+import numpy as np
+
 import vispy.scene as scene
 from vispy.scene.cameras.base_camera import BaseCamera
 from vispy.color import Color
+
 import time
 
 white = Color("#ffffff")

+ 2 - 0
flatcamParsers/ParseDXF.py

@@ -6,6 +6,8 @@
 # ##########################################################
 
 from shapely.geometry import LineString
+from shapely.affinity import rotate
+
 import logging
 
 log = logging.getLogger('base2')

+ 0 - 1
flatcamParsers/ParseDXF_Spline.py

@@ -9,7 +9,6 @@
 # ##########################################################
 
 import math
-import sys
 
 
 def norm(v):

+ 21 - 7
flatcamParsers/ParseExcellon.py

@@ -1,4 +1,14 @@
-from camlib import *
+
+from camlib import Geometry
+import FlatCAMApp
+
+import shapely.affinity as affinity
+from shapely.geometry import Point, LineString
+import numpy as np
+
+import re
+import logging
+import traceback
 
 import FlatCAMTranslation as fcTranslate
 
@@ -8,6 +18,8 @@ import builtins
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
+log = logging.getLogger('base')
+
 
 class Excellon(Geometry):
     """
@@ -1017,10 +1029,10 @@ class Excellon(Geometry):
 
         def bounds_rec(obj):
             if type(obj) is list:
-                minx = Inf
-                miny = Inf
-                maxx = -Inf
-                maxy = -Inf
+                minx = np.Inf
+                miny = np.Inf
+                maxx = -np.Inf
+                maxy = -np.Inf
 
                 for k in obj:
                     if type(k) is dict:
@@ -1087,8 +1099,10 @@ class Excellon(Geometry):
         Scales geometry on the XY plane in the object by a given factor.
         Tool sizes, feedrates an Z-plane dimensions are untouched.
 
-        :param factor: Number by which to scale the object.
-        :type factor: float
+        :param xfactor: Number by which to scale the object.
+        :type xfactor: float
+        :param yfactor: Number by which to scale the object.
+        :type yfactor: float
         :return: None
         :rtype: NOne
         """

+ 2 - 3
flatcamParsers/ParseFont.py

@@ -11,12 +11,11 @@
 # ######################################################################
 
 import re, os, sys, glob
-import itertools
 
 from shapely.geometry import Point, Polygon
-from shapely.affinity import translate, scale, rotate
+from shapely.affinity import translate, scale
 from shapely.geometry import MultiPolygon
-from shapely.geometry.base import BaseGeometry
+
 
 import freetype as ft
 from fontTools import ttLib

+ 37 - 20
flatcamParsers/ParseGerber.py

@@ -1,4 +1,19 @@
-from camlib import *
+
+from camlib import Geometry, arc, arc_angle, ApertureMacro
+import FlatCAMApp
+
+import numpy as np
+import re
+import logging
+import traceback
+from copy import deepcopy
+import sys
+
+from shapely.ops import cascaded_union
+from shapely.geometry import Polygon, MultiPolygon, LineString, Point
+import shapely.affinity as affinity
+from shapely.geometry import box as shply_box
+
 import FlatCAMTranslation as fcTranslate
 
 import gettext
@@ -7,6 +22,8 @@ import builtins
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
+log = logging.getLogger('base')
+
 
 class Gerber(Geometry):
     """
@@ -253,14 +270,14 @@ class Gerber(Geometry):
             self.apertures[apid] = {"type": "R",
                                     "width": float(paramList[0]),
                                     "height": float(paramList[1]),
-                                    "size": sqrt(float(paramList[0]) ** 2 + float(paramList[1]) ** 2)}  # Hack
+                                    "size": np.sqrt(float(paramList[0]) ** 2 + float(paramList[1]) ** 2)}  # Hack
             return apid
 
         if apertureType == "O":  # Obround
             self.apertures[apid] = {"type": "O",
                                     "width": float(paramList[0]),
                                     "height": float(paramList[1]),
-                                    "size": sqrt(float(paramList[0]) ** 2 + float(paramList[1]) ** 2)}  # Hack
+                                    "size": np.sqrt(float(paramList[0]) ** 2 + float(paramList[1]) ** 2)}  # Hack
             return apid
 
         if apertureType == "P":  # Polygon (regular)
@@ -1231,15 +1248,15 @@ class Gerber(Geometry):
 
                     if quadrant_mode == 'MULTI':
                         center = [i + current_x, j + current_y]
-                        radius = sqrt(i ** 2 + j ** 2)
-                        start = arctan2(-j, -i)  # Start angle
+                        radius = np.sqrt(i ** 2 + j ** 2)
+                        start = np.arctan2(-j, -i)  # Start angle
                         # Numerical errors might prevent start == stop therefore
                         # we check ahead of time. This should result in a
                         # 360 degree arc.
                         if current_x == circular_x and current_y == circular_y:
                             stop = start
                         else:
-                            stop = arctan2(-center[1] + circular_y, -center[0] + circular_x)  # Stop angle
+                            stop = np.arctan2(-center[1] + circular_y, -center[0] + circular_x)  # Stop angle
 
                         this_arc = arc(center, radius, start, stop,
                                        arcdir[current_interpolation_mode],
@@ -1273,10 +1290,10 @@ class Gerber(Geometry):
                         valid = False
                         log.debug("I: %f  J: %f" % (i, j))
                         for center in center_candidates:
-                            radius = sqrt(i ** 2 + j ** 2)
+                            radius = np.sqrt(i ** 2 + j ** 2)
 
                             # Make sure radius to start is the same as radius to end.
-                            radius2 = sqrt((center[0] - circular_x) ** 2 + (center[1] - circular_y) ** 2)
+                            radius2 = np.sqrt((center[0] - circular_x) ** 2 + (center[1] - circular_y) ** 2)
                             if radius2 < radius * 0.95 or radius2 > radius * 1.05:
                                 continue  # Not a valid center.
 
@@ -1284,16 +1301,16 @@ class Gerber(Geometry):
                             i = center[0] - current_x
                             j = center[1] - current_y
 
-                            start = arctan2(-j, -i)  # Start angle
-                            stop = arctan2(-center[1] + circular_y, -center[0] + circular_x)  # Stop angle
+                            start = np.arctan2(-j, -i)  # Start angle
+                            stop = np.arctan2(-center[1] + circular_y, -center[0] + circular_x)  # Stop angle
                             angle = abs(arc_angle(start, stop, arcdir[current_interpolation_mode]))
                             log.debug("ARC START: %f, %f  CENTER: %f, %f  STOP: %f, %f" %
                                       (current_x, current_y, center[0], center[1], circular_x, circular_y))
                             log.debug("START Ang: %f, STOP Ang: %f, DIR: %s, ABS: %.12f <= %.12f: %s" %
-                                      (start * 180 / pi, stop * 180 / pi, arcdir[current_interpolation_mode],
-                                       angle * 180 / pi, pi / 2 * 180 / pi, angle <= (pi + 1e-6) / 2))
+                                      (start * 180 / np.pi, stop * 180 / np.pi, arcdir[current_interpolation_mode],
+                                       angle * 180 / np.pi, np.pi / 2 * 180 / np.pi, angle <= (np.pi + 1e-6) / 2))
 
-                            if angle <= (pi + 1e-6) / 2:
+                            if angle <= (np.pi + 1e-6) / 2:
                                 log.debug("########## ACCEPTING ARC ############")
                                 this_arc = arc(center, radius, start, stop,
                                                arcdir[current_interpolation_mode],
@@ -1367,7 +1384,7 @@ class Gerber(Geometry):
 
             # this treats the case when we are storing geometry as solids
 
-            if len(poly_buffer) == 0:
+            if len(poly_buffer) == 0 and len(self.solid_geometry) == 0:
                 log.error("Object is not Gerber file or empty. Aborting Object creation.")
                 return 'fail'
 
@@ -1478,8 +1495,8 @@ class Gerber(Geometry):
             n_vertices = aperture['nVertices']
             points = []
             for i in range(0, n_vertices):
-                x = loc[0] + 0.5 * diam * (cos(2 * pi * i / n_vertices))
-                y = loc[1] + 0.5 * diam * (sin(2 * pi * i / n_vertices))
+                x = loc[0] + 0.5 * diam * (np.cos(2 * np.pi * i / n_vertices))
+                y = loc[1] + 0.5 * diam * (np.sin(2 * np.pi * i / n_vertices))
                 points.append((x, y))
             ply = Polygon(points)
             if 'rotation' in aperture:
@@ -1553,10 +1570,10 @@ class Gerber(Geometry):
 
         def bounds_rec(obj):
             if type(obj) is list and type(obj) is not MultiPolygon:
-                minx = Inf
-                miny = Inf
-                maxx = -Inf
-                maxy = -Inf
+                minx = np.Inf
+                miny = np.Inf
+                maxx = -np.Inf
+                maxy = -np.Inf
 
                 for k in obj:
                     if type(k) is dict:

+ 1 - 1
flatcamParsers/ParseSVG.py

@@ -23,7 +23,7 @@
 from svg.path import Line, Arc, CubicBezier, QuadraticBezier, parse_path
 from svg.path.path import Move
 from shapely.geometry import LineString
-from shapely.affinity import skew, affine_transform
+from shapely.affinity import skew, affine_transform, rotate
 import numpy as np
 
 from flatcamParsers.ParseFont import *

+ 4 - 3
flatcamTools/ToolCalculators.py

@@ -5,8 +5,9 @@
 # MIT Licence                                              #
 # ##########################################################
 
+from PyQt5 import QtWidgets
 from FlatCAMTool import FlatCAMTool
-from FlatCAMObj import *
+from flatcamGUI.GUIElements import FCSpinner, FCDoubleSpinner, FCEntry
 import math
 
 import gettext
@@ -321,11 +322,11 @@ class ToolCalculator(FlatCAMTool):
 
     def on_calculate_inch_units(self):
         mm_val = float(self.mm_entry.get_value())
-        self.inch_entry.set_value('%.*f' % (self.decimals,(mm_val / 25.4)))
+        self.inch_entry.set_value('%.*f' % (self.decimals, (mm_val / 25.4)))
 
     def on_calculate_mm_units(self):
         inch_val = float(self.inch_entry.get_value())
-        self.mm_entry.set_value('%.*f' % (self.decimals,(inch_val * 25.4)))
+        self.mm_entry.set_value('%.*f' % (self.decimals, (inch_val * 25.4)))
 
     def on_calculate_eplate(self):
         length = float(self.pcblength_entry.get_value())

+ 14 - 3
flatcamTools/ToolCutOut.py

@@ -5,12 +5,21 @@
 # MIT Licence                                              #
 # ##########################################################
 
+from PyQt5 import QtWidgets, QtGui, QtCore
 from FlatCAMTool import FlatCAMTool
-from ObjectCollection import *
-from FlatCAMApp import *
-from shapely.geometry import box
+from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, RadioSet, FCComboBox
+from FlatCAMObj import FlatCAMGerber
+
+from shapely.geometry import box, MultiPolygon, Polygon, LineString, LinearRing
 from shapely.ops import cascaded_union, unary_union
+import shapely.affinity as affinity
+
+from matplotlib.backend_bases import KeyEvent as mpl_key_event
 
+from numpy import Inf
+from copy import deepcopy
+import math
+import logging
 import gettext
 import FlatCAMTranslation as fcTranslate
 import builtins
@@ -19,6 +28,8 @@ fcTranslate.apply_language('strings')
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
+log = logging.getLogger('base')
+
 
 class CutOut(FlatCAMTool):
 

+ 9 - 2
flatcamTools/ToolDblSided.py

@@ -1,9 +1,14 @@
+
+from PyQt5 import QtWidgets, QtCore
+
 from FlatCAMTool import FlatCAMTool
-from FlatCAMObj import *
+from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, EvalEntry
+from FlatCAMObj import FlatCAMGerber, FlatCAMExcellon, FlatCAMGeometry
+
 from shapely.geometry import Point
 from shapely import affinity
-from PyQt5 import QtCore
 
+import logging
 import gettext
 import FlatCAMTranslation as fcTranslate
 import builtins
@@ -12,6 +17,8 @@ fcTranslate.apply_language('strings')
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
+log = logging.getLogger('base')
+
 
 class DblSidedTool(FlatCAMTool):
 

+ 9 - 4
flatcamTools/ToolDistance.py

@@ -5,12 +5,15 @@
 # MIT Licence                                              #
 # ##########################################################
 
+from PyQt5 import QtWidgets, QtCore
+
 from FlatCAMTool import FlatCAMTool
-from FlatCAMObj import *
 from flatcamGUI.VisPyVisuals import *
+from flatcamGUI.GUIElements import FCEntry
 
-from math import sqrt
-
+import copy
+import math
+import logging
 import gettext
 import FlatCAMTranslation as fcTranslate
 import builtins
@@ -19,6 +22,8 @@ fcTranslate.apply_language('strings')
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
+log = logging.getLogger('base')
+
 
 class Distance(FlatCAMTool):
 
@@ -335,7 +340,7 @@ class Distance(FlatCAMTool):
             elif len(self.points) == 2:
                 dx = self.points[1][0] - self.points[0][0]
                 dy = self.points[1][1] - self.points[0][1]
-                d = sqrt(dx ** 2 + dy ** 2)
+                d = math.sqrt(dx ** 2 + dy ** 2)
                 self.stop_entry.set_value("(%.*f, %.*f)" % (self.decimals, pos[0], self.decimals, pos[1]))
 
                 self.app.inform.emit(_("MEASURING: Result D(x) = {d_x} | D(y) = {d_y} | Distance = {d_z}").format(

+ 8 - 4
flatcamTools/ToolDistanceMin.py

@@ -5,14 +5,16 @@
 # MIT Licence                                              #
 # ##########################################################
 
+from PyQt5 import QtWidgets, QtCore
 from FlatCAMTool import FlatCAMTool
-from FlatCAMObj import *
 from flatcamGUI.VisPyVisuals import *
+from flatcamGUI.GUIElements import FCEntry
 
 from shapely.ops import nearest_points
+from shapely.geometry import Point
 
-from math import sqrt
-
+import math
+import logging
 import gettext
 import FlatCAMTranslation as fcTranslate
 import builtins
@@ -21,6 +23,8 @@ fcTranslate.apply_language('strings')
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
+log = logging.getLogger('base')
+
 
 class DistanceMin(FlatCAMTool):
 
@@ -260,7 +264,7 @@ class DistanceMin(FlatCAMTool):
         except Exception as e:
             pass
 
-        d = sqrt(dx ** 2 + dy ** 2)
+        d = math.sqrt(dx ** 2 + dy ** 2)
         self.total_distance_entry.set_value('%.*f' % (self.decimals, abs(d)))
 
         self.h_point = (min(first_pos.x, last_pos.x) + (abs(dx) / 2), min(first_pos.y, last_pos.y) + (abs(dy) / 2))

+ 11 - 8
flatcamTools/ToolFilm.py

@@ -5,14 +5,15 @@
 # MIT Licence                                              #
 # ##########################################################
 
-from FlatCAMTool import FlatCAMTool
-from FlatCAMObj import *
+from PyQt5 import QtGui, QtCore, QtWidgets
 
+from FlatCAMTool import FlatCAMTool
 from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, \
     OptionalHideInputSection, OptionalInputSection
-from PyQt5 import QtGui, QtCore, QtWidgets
 
 from copy import deepcopy
+import logging
+from shapely.geometry import Polygon, MultiPolygon, Point
 
 import gettext
 import FlatCAMTranslation as fcTranslate
@@ -22,6 +23,8 @@ fcTranslate.apply_language('strings')
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
+log = logging.getLogger('base')
+
 
 class Film(FlatCAMTool):
 
@@ -166,7 +169,7 @@ class Film(FlatCAMTool):
         self.ois_scale = OptionalInputSection(self.film_scale_cb, [self.film_scalex_label, self.film_scalex_entry,
                                                                    self.film_scaley_label,  self.film_scaley_entry])
         # Skew Geometry
-        self.film_skew_cb =FCCheckBox('%s' % _("Skew Film geometry"))
+        self.film_skew_cb = FCCheckBox('%s' % _("Skew Film geometry"))
         self.film_skew_cb.setToolTip(
             _("Positive values will skew to the right\n"
               "while negative values will skew to the left.")
@@ -202,9 +205,9 @@ class Film(FlatCAMTool):
               "It can be one of the four points of the geometry bounding box.")
         )
         self.film_skew_reference = RadioSet([{'label': _('Bottom Left'), 'value': 'bottomleft'},
-                                          {'label': _('Top Left'), 'value': 'topleft'},
-                                          {'label': _('Bottom Right'), 'value': 'bottomright'},
-                                          {'label': _('Top right'), 'value': 'topright'}],
+                                             {'label': _('Top Left'), 'value': 'topleft'},
+                                             {'label': _('Bottom Right'), 'value': 'bottomright'},
+                                             {'label': _('Top right'), 'value': 'topright'}],
                                             orientation='vertical',
                                             stretch=False)
 
@@ -331,7 +334,7 @@ class Film(FlatCAMTool):
 
         self.exc_label = QtWidgets.QLabel('%s:' % _("Excellon Obj"))
         self.exc_label.setToolTip(
-            _("Remove the geometry of Excellon from the Film to create tge holes in pads.")
+            _("Remove the geometry of Excellon from the Film to create the holes in pads.")
         )
         self.exc_combo = QtWidgets.QComboBox()
         self.exc_combo.setModel(self.app.collection)

+ 2 - 2
flatcamTools/ToolImage.py

@@ -5,10 +5,10 @@
 # MIT Licence                                              #
 # ##########################################################
 
-from FlatCAMTool import FlatCAMTool
+from PyQt5 import QtGui, QtWidgets
 
+from FlatCAMTool import FlatCAMTool
 from flatcamGUI.GUIElements import RadioSet, FCComboBox, FCSpinner
-from PyQt5 import QtGui, QtWidgets
 
 import gettext
 import FlatCAMTranslation as fcTranslate

+ 6 - 3
flatcamTools/ToolMove.py

@@ -5,12 +5,13 @@
 # MIT Licence                                              #
 # ##########################################################
 
+from PyQt5 import QtWidgets, QtCore
 from FlatCAMTool import FlatCAMTool
-from FlatCAMObj import *
 from flatcamGUI.VisPyVisuals import *
+from FlatCAMObj import FlatCAMGerber
 
 from copy import copy
-
+import logging
 import gettext
 import FlatCAMTranslation as fcTranslate
 import builtins
@@ -19,11 +20,13 @@ fcTranslate.apply_language('strings')
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
+log = logging.getLogger('base')
+
 
 class ToolMove(FlatCAMTool):
 
     toolName = _("Move")
-    replot_signal = pyqtSignal(list)
+    replot_signal = QtCore.pyqtSignal(list)
 
     def __init__(self, app):
         FlatCAMTool.__init__(self, app)

+ 31 - 10
flatcamTools/ToolNonCopperClear.py

@@ -5,12 +5,23 @@
 # MIT Licence                                              #
 # ##########################################################
 
+from PyQt5 import QtWidgets, QtCore, QtGui
 from FlatCAMTool import FlatCAMTool
-from copy import copy, deepcopy
-from ObjectCollection import *
-import time
+from flatcamGUI.GUIElements import FCCheckBox, FCDoubleSpinner, RadioSet, FCTable, FCInputDialog
+from flatcamParsers.ParseGerber import Gerber
+from FlatCAMObj import FlatCAMGeometry, FlatCAMGerber
+import FlatCAMApp
+
+from copy import deepcopy
+
+import numpy as np
+import math
 from shapely.geometry import base
+from shapely.ops import cascaded_union
+from shapely.geometry import MultiPolygon, Polygon, MultiLineString, LineString, LinearRing
 
+import logging
+import traceback
 import gettext
 import FlatCAMTranslation as fcTranslate
 import builtins
@@ -19,6 +30,8 @@ fcTranslate.apply_language('strings')
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
+log = logging.getLogger('base')
+
 
 class NonCopperClear(FlatCAMTool, Gerber):
 
@@ -261,7 +274,6 @@ class NonCopperClear(FlatCAMTool, Gerber):
         )
 
         grid2.addWidget(self.addtool_btn, 0, 0)
-        # grid2.addWidget(self.copytool_btn, 0, 1)
         grid2.addWidget(self.deltool_btn, 0, 2)
 
         self.empty_label_0 = QtWidgets.QLabel('')
@@ -269,6 +281,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
         grid3 = QtWidgets.QGridLayout()
         self.tools_box.addLayout(grid3)
+        grid3.setColumnStretch(0, 0)
+        grid3.setColumnStretch(1, 1)
 
         e_lab_1 = QtWidgets.QLabel('<b>%s:</b>' % _("Parameters"))
         grid3.addWidget(e_lab_1, 0, 0)
@@ -472,7 +486,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
             "Add", self.on_add_tool_by_key, icon=QtGui.QIcon("share/plus16.png"))
         self.tools_table.addContextMenu(
             "Delete", lambda:
-            self.on_tool_delete(rows_to_delete=None, all=None), icon=QtGui.QIcon("share/delete32.png"))
+            self.on_tool_delete(rows_to_delete=None, all_tools=None), icon=QtGui.QIcon("share/delete32.png"))
 
         # #############################################################################
         # ########################## VARIABLES ########################################
@@ -739,7 +753,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                     tool_type_item = QtWidgets.QComboBox()
                     for item in self.tool_type_item_options:
                         tool_type_item.addItem(item)
-                        tool_type_item.setStyleSheet('background-color: rgb(255,255,255)')
+                        # tool_type_item.setStyleSheet('background-color: rgb(255,255,255)')
                     idx = tool_type_item.findText(tooluid_value['tool_type'])
                     tool_type_item.setCurrentIndex(idx)
 
@@ -747,9 +761,9 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
                     operation_type = QtWidgets.QComboBox()
                     operation_type.addItem('iso_op')
-                    operation_type.setStyleSheet('background-color: rgb(255,255,255)')
+                    # operation_type.setStyleSheet('background-color: rgb(255,255,255)')
                     operation_type.addItem('clear_op')
-                    operation_type.setStyleSheet('background-color: rgb(255,255,255)')
+                    # operation_type.setStyleSheet('background-color: rgb(255,255,255)')
                     op_idx = operation_type.findText(tooluid_value['operation'])
                     operation_type.setCurrentIndex(op_idx)
 
@@ -1039,12 +1053,19 @@ class NonCopperClear(FlatCAMTool, Gerber):
                                                               "New diameter value is already in the Tool Table."))
         self.build_ui()
 
-    def on_tool_delete(self, rows_to_delete=None, all=None):
+    def on_tool_delete(self, rows_to_delete=None, all_tools=None):
+        """
+        Will delete a tool in the tool table
+
+        :param rows_to_delete: which rows to delete; can be a list
+        :param all_tools: delete all tools in the tool table
+        :return:
+        """
         self.ui_disconnect()
 
         deleted_tools_list = []
 
-        if all:
+        if all_tools:
             self.paint_tools.clear()
             self.build_ui()
             return

+ 14 - 6
flatcamTools/ToolOptimal.py

@@ -5,13 +5,19 @@
 # MIT Licence                                              #
 # ##########################################################
 
+from PyQt5 import QtWidgets, QtCore, QtGui
+
 from FlatCAMTool import FlatCAMTool
-from FlatCAMObj import *
-from shapely.geometry import Point
-from shapely import affinity
+from flatcamGUI.GUIElements import OptionalHideInputSection, FCTextArea, FCEntry, FCSpinner, FCCheckBox
+from FlatCAMObj import FlatCAMGerber
+import FlatCAMApp
+
+from shapely.geometry import MultiPolygon
 from shapely.ops import nearest_points
-from PyQt5 import QtCore
 
+import numpy as np
+
+import logging
 import gettext
 import FlatCAMTranslation as fcTranslate
 import builtins
@@ -20,13 +26,15 @@ fcTranslate.apply_language('strings')
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
+log = logging.getLogger('base')
+
 
 class ToolOptimal(FlatCAMTool):
 
     toolName = _("Optimal Tool")
 
-    update_text = pyqtSignal(list)
-    update_sec_distances = pyqtSignal(dict)
+    update_text = QtCore.pyqtSignal(list)
+    update_sec_distances = QtCore.pyqtSignal(dict)
 
     def __init__(self, app):
         FlatCAMTool.__init__(self, app)

+ 9 - 4
flatcamTools/ToolPDF.py

@@ -5,19 +5,22 @@
 # MIT Licence                                              #
 # ##########################################################
 
+from PyQt5 import QtWidgets, QtCore
+
 from FlatCAMTool import FlatCAMTool
-from shapely.geometry import Point, Polygon, LineString
-from shapely.ops import cascaded_union, unary_union
+import FlatCAMApp
 
-from FlatCAMObj import *
+from shapely.geometry import Point, Polygon, LineString, MultiPolygon
+from shapely.ops import unary_union
 
-import math
 from copy import copy, deepcopy
 import numpy as np
 
 import zlib
 import re
 import time
+import logging
+import traceback
 
 import gettext
 import FlatCAMTranslation as fcTranslate
@@ -27,6 +30,8 @@ fcTranslate.apply_language('strings')
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
+log = logging.getLogger('base')
+
 
 class ToolPDF(FlatCAMTool):
     """

+ 27 - 9
flatcamTools/ToolPaint.py

@@ -5,10 +5,25 @@
 # MIT Licence                                              #
 # ##########################################################
 
+from PyQt5 import QtWidgets, QtGui, QtCore
+from PyQt5.QtCore import Qt
+
 from FlatCAMTool import FlatCAMTool
-from copy import copy, deepcopy
-from ObjectCollection import *
-from shapely.geometry import base
+from copy import deepcopy
+# from ObjectCollection import *
+from flatcamParsers.ParseGerber import Gerber
+from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry
+from camlib import Geometry
+from flatcamGUI.GUIElements import FCTable, FCDoubleSpinner, FCCheckBox, FCInputDialog, RadioSet
+import FlatCAMApp
+
+from shapely.geometry import base, Polygon, MultiPolygon, LinearRing
+from shapely.ops import cascaded_union
+
+import numpy as np
+from numpy import Inf
+import traceback
+import logging
 
 import gettext
 import FlatCAMTranslation as fcTranslate
@@ -18,6 +33,8 @@ fcTranslate.apply_language('strings')
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
+log = logging.getLogger('base')
+
 
 class ToolPaint(FlatCAMTool, Gerber):
 
@@ -374,6 +391,7 @@ class ToolPaint(FlatCAMTool, Gerber):
 
         self.mm = None
         self.mp = None
+        self.mr = None
 
         self.sel_rect = []
 
@@ -641,10 +659,10 @@ class ToolPaint(FlatCAMTool, Gerber):
             for tooluid_key, tooluid_value in self.paint_tools.items():
                 if float('%.*f' % (self.decimals, tooluid_value['tooldia'])) == tool_sorted:
                     tool_id += 1
-                    id = QtWidgets.QTableWidgetItem('%d' % int(tool_id))
-                    id.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+                    id_item = QtWidgets.QTableWidgetItem('%d' % int(tool_id))
+                    id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
                     row_no = tool_id - 1
-                    self.tools_table.setItem(row_no, 0, id)  # Tool name/id
+                    self.tools_table.setItem(row_no, 0, id_item)  # Tool name/id
 
                     # Make sure that the drill diameter when in MM is with no more than 2 decimals
                     # There are no drill bits in MM with more than 2 decimals diameter
@@ -657,7 +675,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                     tool_type_item = QtWidgets.QComboBox()
                     for item in self.tool_type_item_options:
                         tool_type_item.addItem(item)
-                        tool_type_item.setStyleSheet('background-color: rgb(255,255,255)')
+                        # tool_type_item.setStyleSheet('background-color: rgb(255,255,255)')
                     idx = tool_type_item.findText(tooluid_value['tool_type'])
                     tool_type_item.setCurrentIndex(idx)
 
@@ -2213,7 +2231,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                         log.debug("Could not Paint the polygons. %s" % str(e))
                         self.app.inform.emit('[ERROR] %s\n%s' %
                                              (_("Could not do Paint All. Try a different combination of parameters. "
-                                               "Or a different Method of paint"), str(e)))
+                                                "Or a different Method of paint"), str(e)))
                         return
 
                     pol_nr += 1
@@ -2373,7 +2391,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                         log.debug("Could not Paint the polygons. %s" % str(e))
                         self.app.inform.emit('[ERROR] %s\n%s' %
                                              (_("Could not do Paint All. Try a different combination of parameters. "
-                                               "Or a different Method of paint"), str(e)))
+                                                "Or a different Method of paint"), str(e)))
                         return
 
                     pol_nr += 1

+ 31 - 13
flatcamTools/ToolPanelize.py

@@ -5,19 +5,29 @@
 # MIT Licence                                              #
 # ##########################################################
 
+from PyQt5 import QtWidgets, QtGui, QtCore
 from FlatCAMTool import FlatCAMTool
-from copy import copy, deepcopy
-from ObjectCollection import *
-import time
+
+from flatcamGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet, FCCheckBox, OptionalInputSection
+from FlatCAMObj import FlatCAMGeometry, FlatCAMGerber, FlatCAMExcellon
+import FlatCAMApp
+from copy import deepcopy
+# from ObjectCollection import *
+import numpy as np
+
+import shapely.affinity as affinity
 
 import gettext
 import FlatCAMTranslation as fcTranslate
 import builtins
+import logging
 
 fcTranslate.apply_language('strings')
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
+log = logging.getLogger('base')
+
 
 class Panelize(FlatCAMTool):
 
@@ -367,15 +377,13 @@ class Panelize(FlatCAMTool):
 
         # Get source object.
         try:
-            obj = self.app.collection.get_by_name(str(name))
+            panel_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))
             return "Could not retrieve object: %s" % name
 
-        panel_obj = obj
-
         if panel_obj is None:
             self.app.inform.emit('[ERROR_NOTCL] %s: %s' %
                                  (_("Object not found"), panel_obj))
@@ -443,6 +451,18 @@ class Panelize(FlatCAMTool):
                     rows -= 1
                     panel_lengthy = ((ymax - ymin) * rows) + (spacing_rows * (rows - 1))
 
+        if isinstance(panel_obj, FlatCAMExcellon) or isinstance(panel_obj, FlatCAMGeometry):
+            # make a copy of the panelized Excellon or Geometry tools
+            copied_tools = dict()
+            for tt, tt_val in list(panel_obj.tools.items()):
+                copied_tools[tt] = deepcopy(tt_val)
+
+        if isinstance(panel_obj, FlatCAMGerber):
+            # make a copy of the panelized Gerber apertures
+            copied_apertures = dict()
+            for tt, tt_val in list(panel_obj.apertures.items()):
+                copied_apertures[tt] = deepcopy(tt_val)
+
         def panelize_2():
             if panel_obj is not None:
                 self.app.inform.emit(_("Generating panel ... "))
@@ -452,7 +472,7 @@ class Panelize(FlatCAMTool):
                 def job_init_excellon(obj_fin, app_obj):
                     currenty = 0.0
                     self.app.progress.emit(10)
-                    obj_fin.tools = panel_obj.tools.copy()
+                    obj_fin.tools = copied_tools
                     obj_fin.drills = []
                     obj_fin.slots = []
                     obj_fin.solid_geometry = []
@@ -472,7 +492,6 @@ class Panelize(FlatCAMTool):
                         currentx = 0.0
                         for col in range(columns):
                             element += 1
-                            disp_number = 0
                             old_disp_number = 0
 
                             if panel_obj.drills:
@@ -493,7 +512,7 @@ class Panelize(FlatCAMTool):
                                     drill_nr += 1
                                     disp_number = int(np.interp(drill_nr, [0, geo_len_drills], [0, 100]))
 
-                                    if disp_number > old_disp_number and disp_number <= 100:
+                                    if old_disp_number < disp_number <= 100:
                                         self.app.proc_container.update_view_text(' %s: %d D:%d%%' %
                                                                                  (_("Copy"),
                                                                                   int(element),
@@ -520,7 +539,7 @@ class Panelize(FlatCAMTool):
                                     slot_nr += 1
                                     disp_number = int(np.interp(slot_nr, [0, geo_len_slots], [0, 100]))
 
-                                    if disp_number > old_disp_number and disp_number <= 100:
+                                    if old_disp_number < disp_number <= 100:
                                         self.app.proc_container.update_view_text(' %s: %d S:%d%%' %
                                                                                  (_("Copy"),
                                                                                   int(element),
@@ -557,12 +576,12 @@ class Panelize(FlatCAMTool):
                     # create the initial structure on which to create the panel
                     if isinstance(panel_obj, FlatCAMGeometry):
                         obj_fin.multigeo = panel_obj.multigeo
-                        obj_fin.tools = deepcopy(panel_obj.tools)
+                        obj_fin.tools = copied_tools
                         if panel_obj.multigeo is True:
                             for tool in panel_obj.tools:
                                 obj_fin.tools[tool]['solid_geometry'][:] = []
                     elif isinstance(panel_obj, FlatCAMGerber):
-                        obj_fin.apertures = deepcopy(panel_obj.apertures)
+                        obj_fin.apertures = copied_apertures
                         for ap in obj_fin.apertures:
                             obj_fin.apertures[ap]['geometry'] = list()
 
@@ -594,7 +613,6 @@ class Panelize(FlatCAMTool):
 
                         for col in range(columns):
                             element += 1
-                            disp_number = 0
                             old_disp_number = 0
 
                             if isinstance(panel_obj, FlatCAMGeometry):

+ 4 - 4
flatcamTools/ToolPcbWizard.py

@@ -5,11 +5,11 @@
 # MIT Licence                                              #
 # ##########################################################
 
+from PyQt5 import QtWidgets, QtCore
+
 from FlatCAMTool import FlatCAMTool
+from flatcamGUI.GUIElements import RadioSet, FCSpinner, FCButton, FCTable
 
-from flatcamGUI.GUIElements import RadioSet, FCComboBox, FCSpinner, FCButton, FCTable
-from PyQt5 import QtGui, QtWidgets, QtCore
-from PyQt5.QtCore import pyqtSignal
 import re
 import os
 from datetime import datetime
@@ -26,7 +26,7 @@ if '_' not in builtins.__dict__:
 
 class PcbWizard(FlatCAMTool):
 
-    file_loaded = pyqtSignal(str, str)
+    file_loaded = QtCore.pyqtSignal(str, str)
 
     toolName = _("PcbWizard Import Tool")
 

+ 19 - 13
flatcamTools/ToolProperties.py

@@ -6,10 +6,14 @@
 # ##########################################################
 
 from PyQt5 import QtGui, QtCore, QtWidgets
-from PyQt5.QtCore import Qt
 from FlatCAMTool import FlatCAMTool
-from FlatCAMObj import *
+from FlatCAMObj import FlatCAMCNCjob
 
+from shapely.geometry import MultiPolygon, Polygon
+from shapely.ops import cascaded_union
+
+from copy import deepcopy
+import logging
 import gettext
 import FlatCAMTranslation as fcTranslate
 import builtins
@@ -18,11 +22,13 @@ fcTranslate.apply_language('strings')
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
+log = logging.getLogger('base')
+
 
 class Properties(FlatCAMTool):
     toolName = _("Properties")
 
-    calculations_finished = pyqtSignal(float, float, float, float, object)
+    calculations_finished = QtCore.pyqtSignal(float, float, float, float, object)
 
     def __init__(self, app):
         FlatCAMTool.__init__(self, app)
@@ -150,18 +156,18 @@ class Properties(FlatCAMTool):
 
         self.addChild(obj_name, [obj.options['name']])
 
-        def job_thread(obj):
+        def job_thread(obj_prop):
             proc = self.app.proc_container.new(_("Calculating dimensions ... Please wait."))
 
             length = 0.0
             width = 0.0
             area = 0.0
 
-            geo = obj.solid_geometry
+            geo = obj_prop.solid_geometry
             if geo:
                 # calculate physical dimensions
                 try:
-                    xmin, ymin, xmax, ymax = obj.bounds()
+                    xmin, ymin, xmax, ymax = obj_prop.bounds()
 
                     length = abs(xmax - xmin)
                     width = abs(ymax - ymin)
@@ -179,9 +185,9 @@ class Properties(FlatCAMTool):
                 xmax = []
                 ymax = []
 
-                for tool in obj.tools:
+                for tool_k in obj_prop.tools:
                     try:
-                        x0, y0, x1, y1 = cascaded_union(obj.tools[tool]['solid_geometry']).bounds
+                        x0, y0, x1, y1 = cascaded_union(obj_prop.tools[tool_k]['solid_geometry']).bounds
                         xmin.append(x0)
                         ymin.append(y0)
                         xmax.append(x1)
@@ -207,25 +213,25 @@ class Properties(FlatCAMTool):
                     log.debug("Properties.addItems() --> %s" % str(e))
 
             area_chull = 0.0
-            if not isinstance(obj, FlatCAMCNCjob):
+            if not isinstance(obj_prop, FlatCAMCNCjob):
                 # calculate and add convex hull area
                 if geo:
                     if isinstance(geo, MultiPolygon):
                         env_obj = geo.convex_hull
                     elif (isinstance(geo, MultiPolygon) and len(geo) == 1) or \
                             (isinstance(geo, list) and len(geo) == 1) and isinstance(geo[0], Polygon):
-                        env_obj = cascaded_union(obj.solid_geometry)
+                        env_obj = cascaded_union(obj_prop.solid_geometry)
                         env_obj = env_obj.convex_hull
                     else:
-                        env_obj = cascaded_union(obj.solid_geometry)
+                        env_obj = cascaded_union(obj_prop.solid_geometry)
                         env_obj = env_obj.convex_hull
 
                     area_chull = env_obj.area
                 else:
                     try:
                         area_chull = []
-                        for tool in obj.tools:
-                            area_el = cascaded_union(obj.tools[tool]['solid_geometry']).convex_hull
+                        for tool_k in obj_prop.tools:
+                            area_el = cascaded_union(obj_prop.tools[tool_k]['solid_geometry']).convex_hull
                             area_chull.append(area_el.area)
                         area_chull = max(area_chull)
                     except Exception as e:

+ 11 - 6
flatcamTools/ToolRulesCheck.py

@@ -5,15 +5,18 @@
 # MIT Licence                                              #
 # ##########################################################
 
+from PyQt5 import QtWidgets
+
 from FlatCAMTool import FlatCAMTool
-from copy import copy, deepcopy
-from ObjectCollection import *
-import time
+from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, OptionalInputSection
+from copy import deepcopy
+
 from FlatCAMPool import *
-from os import getpid
+# from os import getpid
 from shapely.ops import nearest_points
-from shapely.geometry.base import BaseGeometry
+from shapely.geometry import MultiPolygon, Polygon
 
+import logging
 import gettext
 import FlatCAMTranslation as fcTranslate
 import builtins
@@ -22,12 +25,14 @@ fcTranslate.apply_language('strings')
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
+log = logging.getLogger('base')
+
 
 class RulesCheck(FlatCAMTool):
 
     toolName = _("Check Rules")
 
-    tool_finished = pyqtSignal(list)
+    tool_finished = QtCore.pyqtSignal(list)
 
     def __init__(self, app):
         super(RulesCheck, self).__init__(self)

+ 1 - 1
flatcamTools/ToolShell.py

@@ -6,7 +6,7 @@
 # MIT Licence                                              #
 # ##########################################################
 
-# from PyQt5.QtCore import pyqtSignal
+
 from PyQt5.QtCore import Qt
 from PyQt5.QtGui import QTextCursor
 from PyQt5.QtWidgets import QVBoxLayout, QWidget

+ 1 - 1
flatcamTools/ToolSolderPaste.py

@@ -278,7 +278,7 @@ class SolderPaste(FlatCAMTool):
         )
 
         self.pp_combo = FCComboBox()
-        self.pp_combo.setStyleSheet('background-color: rgb(255,255,255)')
+        # self.pp_combo.setStyleSheet('background-color: rgb(255,255,255)')
         self.gcode_form_layout.addRow(pp_label, self.pp_combo)
 
         # ## Buttons

+ 27 - 19
flatcamTools/ToolSub.py

@@ -5,12 +5,18 @@
 # MIT Licence                                              #
 # ##########################################################
 
+from PyQt5 import QtWidgets, QtCore
 
 from FlatCAMTool import FlatCAMTool
-# from copy import copy, deepcopy
-from ObjectCollection import *
-import time
+from flatcamGUI.GUIElements import FCCheckBox, FCButton
+
+from shapely.geometry import Polygon, MultiPolygon, MultiLineString, LineString
+from shapely.ops import cascaded_union
 
+import traceback
+from copy import deepcopy
+import time
+import logging
 import gettext
 import FlatCAMTranslation as fcTranslate
 import builtins
@@ -19,12 +25,14 @@ fcTranslate.apply_language('strings')
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
+log = logging.getLogger('base')
+
 
 class ToolSub(FlatCAMTool):
 
     job_finished = QtCore.pyqtSignal(bool)
 
-    toolName = _("Substract Tool")
+    toolName = _("Subtract Tool")
 
     def __init__(self, app):
         self.app = app
@@ -64,8 +72,8 @@ class ToolSub(FlatCAMTool):
 
         self.target_gerber_label = QtWidgets.QLabel('%s:' % _("Target"))
         self.target_gerber_label.setToolTip(
-            _("Gerber object from which to substract\n"
-              "the substractor Gerber object.")
+            _("Gerber object from which to subtract\n"
+              "the subtractor Gerber object.")
         )
 
         form_layout.addRow(self.target_gerber_label, self.target_gerber_combo)
@@ -76,9 +84,9 @@ class ToolSub(FlatCAMTool):
         self.sub_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.sub_gerber_combo.setCurrentIndex(1)
 
-        self.sub_gerber_label = QtWidgets.QLabel('%s:' % _("Substractor"))
+        self.sub_gerber_label = QtWidgets.QLabel('%s:' % _("Subtractor"))
         self.sub_gerber_label.setToolTip(
-            _("Gerber object that will be substracted\n"
+            _("Gerber object that will be subtracted\n"
               "from the target Gerber object.")
         )
         e_lab_1 = QtWidgets.QLabel('')
@@ -87,7 +95,7 @@ class ToolSub(FlatCAMTool):
 
         self.intersect_btn = FCButton(_('Substract Gerber'))
         self.intersect_btn.setToolTip(
-            _("Will remove the area occupied by the substractor\n"
+            _("Will remove the area occupied by the subtractor\n"
               "Gerber from the Target Gerber.\n"
               "Can be used to remove the overlapping silkscreen\n"
               "over the soldermask.")
@@ -110,8 +118,8 @@ class ToolSub(FlatCAMTool):
 
         self.target_geo_label = QtWidgets.QLabel('%s:' % _("Target"))
         self.target_geo_label.setToolTip(
-            _("Geometry object from which to substract\n"
-              "the substractor Geometry object.")
+            _("Geometry object from which to subtract\n"
+              "the subtractor Geometry object.")
         )
 
         form_geo_layout.addRow(self.target_geo_label, self.target_geo_combo)
@@ -122,9 +130,9 @@ class ToolSub(FlatCAMTool):
         self.sub_geo_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
         self.sub_geo_combo.setCurrentIndex(1)
 
-        self.sub_geo_label = QtWidgets.QLabel('%s:' % _("Substractor"))
+        self.sub_geo_label = QtWidgets.QLabel('%s:' % _("Subtractor"))
         self.sub_geo_label.setToolTip(
-            _("Geometry object that will be substracted\n"
+            _("Geometry object that will be subtracted\n"
               "from the target Geometry object.")
         )
         e_lab_1 = QtWidgets.QLabel('')
@@ -132,12 +140,12 @@ class ToolSub(FlatCAMTool):
         form_geo_layout.addRow(self.sub_geo_label, self.sub_geo_combo)
 
         self.close_paths_cb = FCCheckBox(_("Close paths"))
-        self.close_paths_cb.setToolTip(_("Checking this will close the paths cut by the Geometry substractor object."))
+        self.close_paths_cb.setToolTip(_("Checking this will close the paths cut by the Geometry subtractor object."))
         self.tools_box.addWidget(self.close_paths_cb)
 
-        self.intersect_geo_btn = FCButton(_('Substract Geometry'))
+        self.intersect_geo_btn = FCButton(_('Subtract Geometry'))
         self.intersect_geo_btn.setToolTip(
-            _("Will remove the area occupied by the substractor\n"
+            _("Will remove the area occupied by the subtractor\n"
               "Geometry from the Target Geometry.")
         )
         self.tools_box.addWidget(self.intersect_geo_btn)
@@ -256,7 +264,7 @@ class ToolSub(FlatCAMTool):
         self.sub_grb_obj_name = self.sub_gerber_combo.currentText()
         if self.sub_grb_obj_name == '':
             self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                 _("No Substractor object loaded."))
+                                 _("No Subtractor object loaded."))
             return
 
         # Get substractor object.
@@ -458,7 +466,7 @@ class ToolSub(FlatCAMTool):
         self.sub_geo_obj_name = self.sub_geo_combo.currentText()
         if self.sub_geo_obj_name == '':
             self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                 _("No Substractor object loaded."))
+                                 _("No Subtractor object loaded."))
             return
 
         # Get substractor object.
@@ -472,7 +480,7 @@ class ToolSub(FlatCAMTool):
 
         if self.sub_geo_obj.multigeo:
             self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                 _("Currently, the Substractor geometry cannot be of type Multigeo."))
+                                 _("Currently, the Subtractor geometry cannot be of type Multigeo."))
             return
 
         # create the target_options obj

+ 5 - 3
flatcamTools/ToolTransform.py

@@ -5,8 +5,10 @@
 # MIT Licence                                              #
 # ##########################################################
 
+from PyQt5 import QtWidgets
 from FlatCAMTool import FlatCAMTool
-from FlatCAMObj import *
+from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCButton, OptionalInputSection, EvalEntry2
+from FlatCAMObj import FlatCAMCNCjob
 
 import gettext
 import FlatCAMTranslation as fcTranslate
@@ -271,7 +273,7 @@ class ToolTransform(FlatCAMTool):
             _("Flip the selected object(s) over the X axis.")
         )
 
-        hlay0= QtWidgets.QHBoxLayout()
+        hlay0 = QtWidgets.QHBoxLayout()
         self.transform_lay.addLayout(hlay0)
 
         hlay0.addWidget(self.flipx_button)
@@ -312,7 +314,7 @@ class ToolTransform(FlatCAMTool):
 
         self.ois_flip = OptionalInputSection(self.flip_ref_cb, [self.flip_ref_entry, self.flip_ref_button], logic=True)
 
-        hlay1= QtWidgets.QHBoxLayout()
+        hlay1 = QtWidgets.QHBoxLayout()
         self.transform_lay.addLayout(hlay1)
 
         hlay1.addWidget(self.flip_ref_label)

BIN
locale/de/LC_MESSAGES/strings.mo


Fichier diff supprimé car celui-ci est trop grand
+ 289 - 262
locale/de/LC_MESSAGES/strings.po


BIN
locale/en/LC_MESSAGES/strings.mo


Fichier diff supprimé car celui-ci est trop grand
+ 312 - 284
locale/en/LC_MESSAGES/strings.po


BIN
locale/es/LC_MESSAGES/strings.mo


Fichier diff supprimé car celui-ci est trop grand
+ 324 - 319
locale/es/LC_MESSAGES/strings.po


BIN
locale/fr/LC_MESSAGES/strings.mo


Fichier diff supprimé car celui-ci est trop grand
+ 324 - 316
locale/fr/LC_MESSAGES/strings.po


BIN
locale/pt_BR/LC_MESSAGES/strings.mo


Fichier diff supprimé car celui-ci est trop grand
+ 322 - 316
locale/pt_BR/LC_MESSAGES/strings.po


BIN
locale/ro/LC_MESSAGES/strings.mo


Fichier diff supprimé car celui-ci est trop grand
+ 309 - 284
locale/ro/LC_MESSAGES/strings.po


BIN
locale/ru/LC_MESSAGES/strings.mo


Fichier diff supprimé car celui-ci est trop grand
+ 296 - 277
locale/ru/LC_MESSAGES/strings.po


BIN
share/close_edit_file16.png


BIN
share/close_edit_file32.png


BIN
share/copy_file16.png


BIN
share/copy_file32.png


BIN
share/delete_file16.png


BIN
share/delete_file32.png


BIN
share/deselect_all32.png


BIN
share/edit_file16.png


BIN
share/edit_file32.png


BIN
share/new_file16.png


BIN
share/new_file32.png


BIN
share/new_file_exc16.png


BIN
share/new_file_exc32.png


BIN
share/new_file_geo16.png


BIN
share/new_file_geo32.png


BIN
share/new_file_grb16.png


BIN
share/new_file_grb32.png


BIN
share/panelize16.png


BIN
share/panelize32.png


BIN
share/project_save16.png


BIN
share/project_save32.png


Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff