Bladeren bron

Merged in Beta_8.994 (pull request #17)

Beta 8.994
Marius Stanciu 5 jaren geleden
bovenliggende
commit
f04d8be50f
100 gewijzigde bestanden met toevoegingen van 6595 en 3578 verwijderingen
  1. 10 10
      Bookmark.py
  2. 232 3
      CHANGELOG.md
  3. 17 16
      appCommon/Common.py
  4. 4 4
      appCommon/bilinear.py
  5. 178 109
      appDatabase.py
  6. 506 120
      appEditors/AppExcEditor.py
  7. 145 137
      appEditors/AppGeoEditor.py
  8. 413 212
      appEditors/AppGerberEditor.py
  9. 4 2
      appEditors/AppTextEditor.py
  10. 7 12
      appEditors/appGCodeEditor.py
  11. 777 22
      appGUI/GUIElements.py
  12. 278 163
      appGUI/MainGUI.py
  13. 162 139
      appGUI/ObjectUI.py
  14. 25 19
      appGUI/PlotCanvas.py
  15. 29 15
      appGUI/PlotCanvasLegacy.py
  16. 3 2
      appGUI/VisPyTesselators.py
  17. 3 2
      appGUI/VisPyVisuals.py
  18. 22 4
      appGUI/preferences/PreferencesUIManager.py
  19. 1 0
      appGUI/preferences/__init__.py
  20. 6 6
      appGUI/preferences/cncjob/CNCJobAdvOptPrefGroupUI.py
  21. 2 2
      appGUI/preferences/cncjob/CNCJobGenPrefGroupUI.py
  22. 1 1
      appGUI/preferences/excellon/ExcellonAdvOptPrefGroupUI.py
  23. 4 4
      appGUI/preferences/excellon/ExcellonEditorPrefGroupUI.py
  24. 2 2
      appGUI/preferences/excellon/ExcellonExpPrefGroupUI.py
  25. 2 2
      appGUI/preferences/excellon/ExcellonGenPrefGroupUI.py
  26. 2 2
      appGUI/preferences/excellon/ExcellonOptPrefGroupUI.py
  27. 5 5
      appGUI/preferences/general/GeneralAppPrefGroupUI.py
  28. 1 141
      appGUI/preferences/general/GeneralGUIPrefGroupUI.py
  29. 113 16
      appGUI/preferences/geometry/GeometryAdvOptPrefGroupUI.py
  30. 12 11
      appGUI/preferences/geometry/GeometryOptPrefGroupUI.py
  31. 1 1
      appGUI/preferences/gerber/GerberAdvOptPrefGroupUI.py
  32. 2 2
      appGUI/preferences/gerber/GerberExpPrefGroupUI.py
  33. 2 2
      appGUI/preferences/gerber/GerberGenPrefGroupUI.py
  34. 95 68
      appGUI/preferences/tools/Tools2CThievingPrefGroupUI.py
  35. 5 5
      appGUI/preferences/tools/Tools2CalPrefGroupUI.py
  36. 6 6
      appGUI/preferences/tools/Tools2EDrillsPrefGroupUI.py
  37. 3 3
      appGUI/preferences/tools/Tools2FiducialsPrefGroupUI.py
  38. 1 1
      appGUI/preferences/tools/Tools2InvertPrefGroupUI.py
  39. 6 6
      appGUI/preferences/tools/Tools2PunchGerberPrefGroupUI.py
  40. 4 4
      appGUI/preferences/tools/Tools2sidedPrefGroupUI.py
  41. 40 29
      appGUI/preferences/tools/ToolsCalculatorsPrefGroupUI.py
  42. 47 20
      appGUI/preferences/tools/ToolsCornersPrefGroupUI.py
  43. 12 12
      appGUI/preferences/tools/ToolsCutoutPrefGroupUI.py
  44. 18 17
      appGUI/preferences/tools/ToolsDrillPrefGroupUI.py
  45. 4 4
      appGUI/preferences/tools/ToolsFilmPrefGroupUI.py
  46. 13 13
      appGUI/preferences/tools/ToolsISOPrefGroupUI.py
  47. 19 21
      appGUI/preferences/tools/ToolsNCCPrefGroupUI.py
  48. 16 16
      appGUI/preferences/tools/ToolsPaintPrefGroupUI.py
  49. 4 4
      appGUI/preferences/tools/ToolsPanelizePrefGroupUI.py
  50. 8 8
      appGUI/preferences/tools/ToolsPreferencesUI.py
  51. 12 12
      appGUI/preferences/tools/ToolsSolderpastePrefGroupUI.py
  52. 7 1
      appGUI/preferences/tools/ToolsSubPrefGroupUI.py
  53. 8 8
      appGUI/preferences/tools/ToolsTransformPrefGroupUI.py
  54. 4 5
      appObjects/AppObject.py
  55. 86 59
      appObjects/FlatCAMCNCJob.py
  56. 2 6
      appObjects/FlatCAMDocument.py
  57. 11 14
      appObjects/FlatCAMExcellon.py
  58. 362 108
      appObjects/FlatCAMGeometry.py
  59. 57 54
      appObjects/FlatCAMGerber.py
  60. 4 4
      appObjects/FlatCAMObj.py
  61. 4 9
      appObjects/FlatCAMScript.py
  62. 12 12
      appObjects/ObjectCollection.py
  63. 10 10
      appParsers/ParseDXF.py
  64. 1 2
      appParsers/ParseDXF_Spline.py
  65. 16 14
      appParsers/ParseFont.py
  66. 61 64
      appParsers/ParseGerber.py
  67. 1 1
      appParsers/ParseHPGL2.py
  68. 71 112
      appParsers/ParsePDF.py
  69. 5 1
      appParsers/ParseSVG.py
  70. 4 2
      appPreProcessor.py
  71. 5 4
      appTools/ToolAlignObjects.py
  72. 197 68
      appTools/ToolCalculators.py
  73. 21 21
      appTools/ToolCalibration.py
  74. 339 280
      appTools/ToolCopperThieving.py
  75. 310 98
      appTools/ToolCorners.py
  76. 56 43
      appTools/ToolCutOut.py
  77. 40 34
      appTools/ToolDblSided.py
  78. 3 2
      appTools/ToolDistance.py
  79. 22 22
      appTools/ToolDrilling.py
  80. 10 13
      appTools/ToolEtchCompensation.py
  81. 40 44
      appTools/ToolExtractDrills.py
  82. 129 101
      appTools/ToolFiducials.py
  83. 20 15
      appTools/ToolFilm.py
  84. 3 4
      appTools/ToolImage.py
  85. 13 15
      appTools/ToolInvertGerber.py
  86. 213 120
      appTools/ToolIsolation.py
  87. 26 26
      appTools/ToolMilling.py
  88. 8 8
      appTools/ToolMove.py
  89. 282 185
      appTools/ToolNCC.py
  90. 2 1
      appTools/ToolOptimal.py
  91. 14 9
      appTools/ToolPDF.py
  92. 93 110
      appTools/ToolPaint.py
  93. 28 29
      appTools/ToolPanelize.py
  94. 5 5
      appTools/ToolPcbWizard.py
  95. 2 2
      appTools/ToolProperties.py
  96. 545 256
      appTools/ToolPunchGerber.py
  97. 63 56
      appTools/ToolQRCode.py
  98. 63 56
      appTools/ToolRulesCheck.py
  99. 3 0
      appTools/ToolShell.py
  100. 30 28
      appTools/ToolSolderPaste.py

+ 10 - 10
Bookmark.py

@@ -240,7 +240,7 @@ class BookmarkManager(QtWidgets.QWidget):
             index_list.append(index)
             title_to_remove = self.table_widget.item(model_index.row(), 1).text()
 
-            if title_to_remove == 'FlatCAM' or title_to_remove == 'Backup Site':
+            if title_to_remove == 'FlatCAM' or title_to_remove == _('Backup Site'):
                 self.app.inform.emit('[WARNING_NOTCL] %s.' % _("This bookmark can not be removed"))
                 self.build_bm_ui()
                 return
@@ -287,12 +287,12 @@ class BookmarkManager(QtWidgets.QWidget):
         date = date.replace(' ', '_')
 
         filter__ = "Text File (*.TXT);;All Files (*.*)"
-        filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Bookmarks"),
-                                                           directory='{l_save}/{n}_{date}'.format(
-                                                                l_save=str(self.app.get_last_save_folder()),
-                                                                n=_("Bookmarks"),
-                                                                date=date),
-                                                           ext_filter=filter__)
+        filename, _f = FCFileSaveDialog.get_saved_filename(
+            caption=_("Export Bookmarks"),
+            directory='{l_save}/{n}_{date}'.format(l_save=str(self.app.get_last_save_folder()),
+                                                   n=_("Bookmarks"),
+                                                   date=date),
+            ext_filter=filter__)
 
         filename = str(filename)
 
@@ -314,9 +314,9 @@ class BookmarkManager(QtWidgets.QWidget):
                 f.close()
             except Exception:
                 e = sys.exc_info()[0]
-                self.app.log.error("Could not load defaults file.")
+                self.app.log.error("Could not load the file.")
                 self.app.log.error(str(e))
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load bookmarks file."))
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load the file."))
                 return
 
             # Save Bookmarks to a file
@@ -346,7 +346,7 @@ class BookmarkManager(QtWidgets.QWidget):
                     bookmarks = f.readlines()
             except IOError:
                 self.app.log.error("Could not load bookmarks file.")
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load bookmarks file."))
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load the file."))
                 return
 
             for line in bookmarks:

+ 232 - 3
CHANGELOG.md

@@ -7,6 +7,235 @@ CHANGELOG for FlatCAM beta
 
 =================================================
 
+7.11.2020
+
+- fixed a small issue in Excellon Editor that reset the delta coordinates on right mouse button click too, which was incorrect. Only left mouse button click should reset the delta coordinates.
+- In Gerber Editor upgraded the UI
+- in Gerber Editor made sure that trying to add a Circular Pad array with null radius will fail
+- in Gerber Editor when the radius is zero the utility geometry is deleted
+- in Excellon Editor made sure that trying to add a Circular Drill/Slot array with null radius will fail
+- in Excellon Editor when the radius is zero the utility geometry is deleted
+- in Gerber Editor fixed an error in the Eraser tool trying to disconnect the Jump signal
+- small UI change in the Isolation Tool for the Reference Object selection
+- small UI changes in NCC Tool and in Paint Tool for the Reference Object selection
+- language strings recompiled to make sure that the .MO files are well optimized
+RELEASE 8.994
+
+6.11.2020
+
+- in Gerber Editor made the selection multithreaded in a bid to get more performance but until Shapely will start working on vectorized geometry this don't yield too much improvement
+- in Gerber Editor, for selection now the intersection of the click point and the geometry is determined for chunks of the original geometry, each chunk gets done in a separate process
+- updated the French translation (by Olivier Cornet)
+- fixed the new InputDialog widget to set its passed values in the constructor
+- in Gerber Editor fixed the Add circular array capability
+- in Gerber Editor remade the utility geometry generation for Circular Pad Array to show the array updated in real time and also fixed the adding of array in negative quadrants
+- in Excellon Editor remade the utility geometry generation for Circular Drill/Slot Array to show the array updated in real time and also fixed the adding of array in negative quadrants
+- Turkish language strings updated (by Mehmet Kaya)
+- both for Excellon and Gerber editor fixed the direction of slots/pads when adding a circular array
+- in Gerber editor added the G key shortcut to toggle the grid snapping
+- made some changes in the Region Tool from the Gerber Editor
+
+5.11.2020
+
+- fixed the annotation plotting in the CNCJob object
+- created a new InputDialog widget that has the buttons and the context menu translated and replaced the old widget throughout the app
+- updated the translation strings
+- Turkish language strings updated
+- set some policy rules back the way they were for the combo boxes in Geometry Object properties
+- updated the Italian translation (by Massimiliano Golfetto)
+- finished the Google-translation of the German language strings
+
+4.11.2020
+
+- updated all the translation files
+- fixed issue with arrays of items could not be added in the Gerber/Excellon Editor when a translation is used
+- fixed issue in the Excellon Editor where the Space key did not toggle the direction of the array of drills
+- combed the application strings all over the app and trimmed them up until those starting with letter 'O'
+- updated the translation strings
+- fixed the UI layout in Excellon Editor and made sure that after changing a value in the Notebook side after the mouse is inside the canvas, the canvas takes the focus allowing the key shortcuts to work
+- Turkish language strings updated (by Mehmet Kaya)
+- in Gerber Editor added the shortcut key 'Space' to change the direction of the array of pads
+- updated all the translation languages. Translated by Google the Spanish, Russian. Romanian translation updated.
+- refactored the name of the classes from the Gerber Editor
+- added more icons in the Gerber and Excellon Editors for the buttons
+
+3.11.2020
+
+- fixed an issue in Tool Isolation used with tools from the Tools Database: the set offset value was not used
+- updated the Tools Database to include all the Geometry keys in the every tool from database
+- made sure that the Operation Type values ('Iso', 'Rough' and 'Finish') are not translated as this may create issues all over the application
+- fix an older issue that made that only the Custom choice created an effect when changing the Offset in the Geometry Object Tool Table
+- trying to optimize Gerber Editor selection with the mouse
+- optimized some of the strings
+- fixed the project context save functionality to work in the new program configuration
+- updated Turkish translation (by Mehmet Kaya)
+- in NCC and Isolation Tools, the Validity Checking of the tools is now multithreaded when the Check Validity UI control is checked
+- translation strings updated
+- fixed an error in Gerber parser, when it encounter a pen-up followed by pen-down move while in a region
+- trimmed the application strings
+- updated the Italian translation (by Massimiliano Golfetto)
+- fixed a series of issues in Gerber Editor tools when the user is trying to use the tools by preselecting a aperture without size (aperture macro)
+- moved all the UI stuff out of the Gerber Editor class in its own class
+- in the Excellon Editor, added shortcut keys Space and Ctrl+Space for toggling the direction of the Slots, respectively for the Array of Slots
+- updated the translation strings to the latest changes in the app strings
+
+2.11.2020
+
+- fixed the Tcl Command AlignDrill
+- fixed the Tcl Command AlignDrillGrid
+- fixed the Tcl COmmand Panelize, Excellon panelization section
+- Fixed an issue in Tool Calibration export_excellon method call
+- PEP8 corrections all over the app
+- made sure that the object selection will not work while in Editors or in the App Tools
+- some minor changes to strings and icons
+- in Corner Markers Tool - the new Gerber object will have also follow_geometry
+- upgraded the Fiducials Tool to create new objects instead of updating in place the source objects
+- upgraded the Copper Thieving Tool to create new objects instead of updating in place the source objects
+- in Copper Thieving Tool added a new parameter to filter areas too small to be desired in the copper thieving; added it to Preferences too
+- Copper Thieving Tool added a new parameter to select what extra geometry to include in the Pattern Plating Mask; added it to the Preferences
+- made a wide change on the spinners GUI ranges: from 9999.9999 all values to 10000.0000
+- fixed some late issues in Corner Markers Tool new feature (messages)
+- upgraded Calculator Tool and added the new parameter is the Preferences
+- updated translation strings
+- fixed borderline bug in Gerber editor when the edited Gerber object last aperture is a aperture without size (Aperture Macro)
+- improved the loading of a Gerber object in the Gerber Editor
+- updated translation strings
+
+1.11.2020
+
+- updated the French Translation (by Olivier Cornet)
+- fixed issue in Corner Markers Tool that crashed the app if only one corner was checked
+- fixed issue in Isolation Tool where Area Isolation selection was not working 
+- added to the translatable strings the category labels in the Project Tab and also updated the translations
+- fixed a small issue (messages) in Corner Markers Tool
+- in Corners Markers Tool added a new feature: possibility to use cross shape markers
+- in Corner Marker Tool add new feature: ability to create an Excellon object with drill holes in the corner markes
+- in Corner Marker Tool, will no longer update the current object with the marker geometry but create a new Gerber object
+- in Join Excellon functionality made sure that the new Combo Exellon object will have copied the data from source objects and not just references, therefore will survive the delete of its parents
+- updated Turkish translation (by Mehmet Kaya)
+- updated all the languages except Turkish
+- in the Tool PDF fixed the creation of Excellon objects to the current Excellon object data structure
+
+31.10.2020
+
+- adapted HPGL importer to work within the new app
+- in Gerber Editor fixed an error when using the Distance Tool with "Snap to center" option active: if clicking not on a pad Distance Tool was not working
+- updated the Turkish translation strings (by Mehmet Kaya)
+- typo fixed in Copper Thieving Tool (due of recent changes)
+- fixed issue #457; wrong reference when saving a project
+- fixed issue in Excellon Editor that crashed the app if using the Resize Drill feature by clicking in menu/toolbar
+- fixed issue in Excellon Editor when using the menu links to Move or Copy Drills/Slots
+- updated the strings 
+- updated the Turkish translation strings (by Mehmet Kaya)
+- added a parent to some of the FCInputDialog widgets used in the app such that those pop-up windows will b displayed in the center of the app main window as opposed to the center of the screen
+- finished the Google-translation of not translated strings in Russian language
+
+30.10.2020
+
+- fixed the Punch Gerber Tool bug that did not allowed the projects to be loaded or to create a new project. Fixed issue #456
+- in Tool Subtract added an option to delete the source objects after a successful operation. Fixed issue #455
+- when entering into an Editor now the Project tab is disabled and the Properties tab where the Editor is installed change the text to 'Editor' and the color is set in Red. After exiting the Tab text is reverted to previous state.
+- fixed and issue where the Tab color that was changed in various states of the app was reverted back to a default color 'black'. Now it reverts to whatever color had before therefore being compatible with an usage of black theme
+- fixed bug that did not allow joining of any object to a Geometry object
+- working on solving the lost triggered signals for the Editor Toolbars buttons after changing the layout
+- fixed issue #454; trigger signals for Editor Toolbars lost after changing the layout
+- updated the translation strings
+- more bugs that were introduced by recent changes done to solve other bugs and so on: fixed issues with the Editors and Delete shortcut
+- fixed an error in the Gerber Editor
+
+29.10.2020
+
+- added icons in most application Tools
+- updated Punch Gerber Tool such that the aperture table is updated upon clicking of the checboxes in Processed Pads Type
+- updated Punch Gerber Tool: the Excellon method now takes into consideration the pads choice 
+- minor change for the FCComboBox UI element by setting its size policy as ignored so it will not expand the notebook when the name of one of its items is very long
+- added a protection on opening the tools database UI if the tools DB file is not loaded
+- fixed NCC Tool not working with the new changes; the check for not having complete isolation is just a Warning
+- fixed the sizePolicy for the FCComboBox widgets in the Preferences that holds the preprocessors
+- fixed issue with how the preamble / postamble GCode were inserted into the final GCode
+- fixed a small issue in GCode Editor where the signals for the buttons were attached again at each launch of the GCode Editor
+- fixed issues in the Tools Database due of recent changes in how the data structure is created
+- made sure that the right tools go only to the intended use, in Tools Database otherwise an error status message is created and Tools DB is closed on adding a wrong tool
+- fixed the usage for Tools Database in Unix-like OS's; fixed issue #453
+- done some modest refactoring
+- fixed the Search and Add feature in Geometry Object UI
+- fixed issue with preamble not being inserted when used alone
+- modified the way that the start GCode is stored such that now the bug in GCode Editor that did not allowed selection of the first tool is now solved
+- in Punch Gerber Tool added a column in the apertures table that allow marking of the selected aperture so the user can see what apertures are selected
+- improvements in the Punch Gerber Tool aperture markings
+- improved the Geometry Object functionality in regards of Tools DB, deleting a tool and adding a tool
+- when using the 'T' shortcut key with Properties Tab in focus and populated with the properties of a Geometry Object made the popped up spinner to have the value autoselected
+- optimized the UI in Extract Drills Tool
+- added some more icons for buttons
+
+28.10.2020
+
+- a series of PEP8 corrections in the FlatCAMGeometry.py
+- in Geometry UI finished a very basic way for the Polish feature (this will be upgraded in the future, for now is very rough)
+- added some new GUI elements by subclassing some widgets for the dialog pop-ups
+- in NCC Tool and Isolation Tool, pressing the shortcut key 'T' will bring the add new tool pop up in which now it is included the button to get the optimal diameter
+- in Geometry UI and for Solderpaste Tool replaced the pop up window that is launched when using shortcut key with one that has the context menu translated
+- some UI cleanup in the Geometry UI
+- updated the translation strings except Russian which could be in the works
+- fixed an error that did not allowed for the older preferences to be deleted when installing a different version of the software
+- in Legacy Mode fixed a small issue: the status bar icon for the Grid axis was not colored on app start
+- added a new string to the translatable strings
+- fixed an error that sometime showed in Legacy Mode when moving the mouse outside canvas
+- reactivated the shortcut key 'S' in TCL Shell, to close the shell dock when it was open (of course the focus has to be not on the command line)
+- brought up-to-date and fixed the Tcl Command Drillcncjob and Cncjob
+- fixed Tcl command Isolate to not print messages on message bar in case it is run headless
+- fixed Tcl command Copper Clear (NCC)
+- fixed Tcl command Paint
+- temporary fix for comboboxes not finding the the value in the items when setting themselves with a value by defaulting to the first item in the list
+- fix in Tool Subtract where there was a typo
+- upgraded the punch Gerber Tool
+- updated the Turkish translation strings (by Mehmet Kaya)
+- fixed an issue in Isolation Tool when running the app in Basic mode;
+- fixed Paint, Isolation and NCC Tools such the translated comboboxes values are now stored as indexes instead of translated words as before
+- in Geometry Object made sure that the widgets in the Tool Table gets populated regardless of encountering non-recognizable translated values
+- in Paint Tool found a small bug and fixed it
+- fixed the Tool Subtractor algorithms
+
+27.10.2020
+
+- created custom classes derived from TextEdit and from LineEdit where I overloaded the context menu and I made all the other classes that were inheriting from them to inherit from those new classes
+- minor fix in ToolsDB2UI
+- updated the Turkish translation strings (by Mehmet Kaya)
+- fixed a bug in conversion of any to Gerber in the section of Excellon conversion
+- some PEP8 fixes
+- fixed a bug due of recent chagnes in FileMenuHandlers class
+- fixed an issue in Tools Database (ToolsDB2 class) that did not made the Tab name in Red color when adding/deleting a tool by using the context menu
+- optimized the Tools Database
+- small string change
+
+26.10.2020
+
+- added a new menu entry and functionality in the View category: enable all non-selected (shortcut key ALT+3)
+- fixed shortcut keys for a number of functionality and in some cases added some new
+- fixed the enable/disable all plots functionality
+- fixed issue with the app window restored in a shifted position after doing Fullscreen
+- fixed issue with coords, delta_coords and status toolbars being disabled when entering fullscreen mode and remaining disabled after restore to normal mode
+- changed some of the strings (added a few in the How To section)
+- more strings updated
+- modified the shortcut strings and the way the shortcuts were listed in the Shortcut keys list such that it will allow a future Shortcuts Manager
+- updated all the language strings according to the modifications done above
+- fixed a small issue with using the shortcut key for toggling the display of Properties tab
+- fixed issue with not updating the model view on the model used in the Project tab when using the shortcut keys 1, 2, 3 which made the color of the tree element not reflect the change in status
+- minor string fix; removed the 'Close' menu entry on the context menu for the TCL Shell
+- overloaded the context menu in the Tcl Shell and added key shortcuts; the menu entries are now translatable
+- overloaded the context menu in several classes from GUI Elements such that the menus are now translated
+- fixed a formatting issue in the MainGUI.py file
+- updated the translations for the new strings that were added
+- another GUI element for which I've overloaded the context menu to make it translatable: _ExpandableTextEdit
+- overloaded the context menu for FCSpinner and for FCDoubleSpinner
+- added new strings and therefore updated the translation strings
+- fixed some minor issues when doing a project save
+
+25.10.2020
+
+- updated the Italian translation (by Massimiliano Golfetto)
+- finished the update of the Spanish translation (Google translate)
+
 24.10.2020
 
 - added a new GUI element, an InputDialog made out of FCSliderWithSpinner named FCInputDialogSlider
@@ -2895,7 +3124,7 @@ RELEASE 8.993
 - added all the tools from Gerber Editor to the the contextual menu
 - added the menu entry "Edit" in the Project contextual menu for Gerber objects
 - started to work in adding slots and slots array in Excellon Editor
-- in FCSlot finished the utility geometry and the GUI for it
+- in SlotAdd finished the utility geometry and the GUI for it
 
 12.08.2019
 
@@ -3183,7 +3412,7 @@ RELEASE 8.993
 
 23.05.2019
 
-- fixed bug in Gerber editor FCDisk and FCSemiDisc that the resulting geometry was not stored into the '0' aperture where all the solids are stored
+- fixed bug in Gerber editor FCDisk and DiscSemiEditorGrb that the resulting geometry was not stored into the '0' aperture where all the solids are stored
 - fixed minor issue in Gerber Editor where apertures were included in the saved object even if there was no geometric data for that aperture
 - some PEP8 cleanup in FlatCAMApp.py
 
@@ -3258,7 +3487,7 @@ RELEASE 8.993
 - made the Feedrate Rapids parameter to depend on the type of preprocessor choosed. It will be showed only for a preprocessor which the name contain 'marlin' and for any preprocessor's that have 'custom' in the name
 - fixed the camlib.Gerber functions of mirror, scale, offset, skew and rotate to work with the new data structure for apertures geometry
 - fixed Gerber Editor selection to work with the new Gerber data structure in self.apertures
-- fixed Gerber Editor FCPad class to work with the new Gerber data structure in self.apertures
+- fixed Gerber Editor PadEditorGrb class to work with the new Gerber data structure in self.apertures
 - fixed camlib.Gerber issues related to what happen after parsing rectangular apertures 
 - wip in camblib.Gerber
 - completely converted the Gerber editor to the new data structure

+ 17 - 16
appCommon/Common.py

@@ -12,7 +12,7 @@
 # ##########################################################
 from PyQt5 import QtCore
 
-from shapely.geometry import Polygon, Point, LineString, MultiPoint
+from shapely.geometry import Polygon, Point, LineString
 from shapely.ops import unary_union
 
 from appGUI.VisPyVisuals import ShapeCollection
@@ -20,7 +20,6 @@ from appTool import AppTool
 
 from copy import deepcopy
 import collections
-import traceback
 
 import numpy as np
 # from voronoi import Voronoi
@@ -39,6 +38,7 @@ class GracefulException(Exception):
     """
     Graceful Exception raised when the user is requesting to cancel the current threaded task
     """
+
     def __init__(self):
         super().__init__()
 
@@ -95,7 +95,7 @@ class LoudUniqueList(list, collections.MutableSequence):
         super().__init__()
         self.callback = lambda x: None
 
-        if not arg is None:
+        if arg is not None:
             if isinstance(arg, list):
                 self.extend(arg)
             else:
@@ -110,32 +110,32 @@ class LoudUniqueList(list, collections.MutableSequence):
     def append(self, v):
         if v in self:
             raise ValueError("One of the added items is already in the list.")
-        l = len(self)
-        self.callback(l)
+        le = len(self)
+        self.callback(le)
         return super().append(v)
 
     def extend(self, t):
         for v in t:
             if v in self:
                 raise ValueError("One of the added items is already in the list.")
-        l = len(self)
-        self.callback(l)
+        le = len(self)
+        self.callback(le)
         return super().extend(t)
 
     def __add__(self, t):  # This is for something like `LoudUniqueList([1, 2, 3]) + list([4, 5, 6])`...
         for v in t:
             if v in self:
                 raise ValueError("One of the added items is already in the list.")
-        l = len(self)
-        self.callback(l)
+        le = len(self)
+        self.callback(le)
         return super().__add__(t)
 
     def __iadd__(self, t):  # This is for something like `l = LoudUniqueList(); l += [1, 2, 3]`
         for v in t:
             if v in self:
                 raise ValueError("One of the added items is already in the list.")
-        l = len(self)
-        self.callback(l)
+        le = len(self)
+        self.callback(le)
         return super().__iadd__(t)
 
     def __setitem__(self, i, v):
@@ -146,7 +146,7 @@ class LoudUniqueList(list, collections.MutableSequence):
         except TypeError:
             if v in self:
                 raise ValueError("One of the modified items is already in the list.")
-        if not v is None:
+        if v is not None:
             self.callback(i)
         return super().__setitem__(i, v)
 
@@ -230,6 +230,7 @@ class ExclusionAreas(QtCore.QObject):
 
         self.app = app
 
+        self.app.log.debug("+ Adding Exclusion Areas")
         # Storage for shapes, storage that can be used by FlatCAm tools for utility geometry
         # VisPy visuals
         if self.app.is_legacy is False:
@@ -373,10 +374,10 @@ class ExclusionAreas(QtCore.QObject):
                     #     "overz":      float < - self.over_z_button
                     # }
                     new_el = {
-                        "obj_type":     self.obj_type,
-                        "shape":        new_rectangle,
-                        "strategy":     self.strategy_button.get_value(),
-                        "overz":        self.over_z_button.get_value()
+                        "obj_type": self.obj_type,
+                        "shape": new_rectangle,
+                        "strategy": self.strategy_button.get_value(),
+                        "overz": self.over_z_button.get_value()
                     }
                     self.exclusion_areas_storage.append(new_el)
 

+ 4 - 4
appCommon/bilinear.py

@@ -71,10 +71,10 @@ class BilinearInterpolation(object):
         self.y_length = y_length
         self.extrapolate = True
 
-        #slopes = self.slopes = []
-        #for j in range(y_length):
-            #intervals = zip(x_index, x_index[1:], values[j], values[j][1:])
-            #slopes.append([(y2 - y1) / (x2 - x1) for x1, x2, y1, y2 in intervals])
+        # slopes = self.slopes = []
+        # for j in range(y_length):
+        #     intervals = zip(x_index, x_index[1:], values[j], values[j][1:])
+        #     slopes.append([(y2 - y1) / (x2 - x1) for x1, x2, y1, y2 in intervals])
 
     def __call__(self, x, y):
         # local lookups

+ 178 - 109
appDatabase.py

@@ -1,6 +1,6 @@
 from PyQt5 import QtGui, QtCore, QtWidgets
 from appGUI.GUIElements import FCEntry, FCButton, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, \
-    FCTree, RadioSet, FCFileSaveDialog, FCLabel
+    FCTree, RadioSet, FCFileSaveDialog, FCLabel, FCComboBox2
 from camlib import to_dict
 
 import sys
@@ -25,6 +25,10 @@ class ToolsDB2UI:
         self.app = app
         self.decimals = self.app.decimals
 
+        self.offset_item_options = ["Path", "In", "Out", "Custom"]
+        self.type_item_options = ['Iso', 'Rough', 'Finish']
+        self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"]
+
         settings = QtCore.QSettings("Open Source", "FlatCAM")
         if settings.contains("machinist"):
             self.machinist_setting = settings.value('machinist', type=int)
@@ -177,20 +181,19 @@ class ToolsDB2UI:
         descript_vlay.addLayout(tools_vlay)
         descript_vlay.addStretch()
 
-        milling_vlay = QtWidgets.QVBoxLayout()
-        milling_vlay.addWidget(self.milling_box)
-        milling_vlay.addStretch()
+        mill_vlay = QtWidgets.QVBoxLayout()
+        mill_vlay.addWidget(self.milling_box)
+        mill_vlay.addStretch()
 
         drilling_vlay = QtWidgets.QVBoxLayout()
         drilling_vlay.addWidget(self.drill_box)
 
         param_hlay.addLayout(descript_vlay)
-        param_hlay.addLayout(milling_vlay)
         param_hlay.addLayout(drilling_vlay)
         param_hlay.addLayout(tools_vlay)
 
         # always visible, always to be included last
-        param_hlay.addLayout(milling_vlay)
+        param_hlay.addLayout(mill_vlay)
 
         param_hlay.addStretch()
 
@@ -219,10 +222,10 @@ class ToolsDB2UI:
         # Tool Dia
         self.dia_label = FCLabel('%s:' % _('Diameter'))
         self.dia_label.setToolTip(
-            _("Tool Diameter."))
+            '%s.' % _("Tool Diameter"))
 
         self.dia_entry = FCDoubleSpinner()
-        self.dia_entry.set_range(-9999.9999, 9999.9999)
+        self.dia_entry.set_range(-10000.0000, 10000.0000)
         self.dia_entry.set_precision(self.decimals)
         self.dia_entry.setObjectName('gdb_dia')
 
@@ -244,7 +247,7 @@ class ToolsDB2UI:
         )
         self.tol_min_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.tol_min_entry.set_precision(self.decimals)
-        self.tol_min_entry.set_range(0, 9999.9999)
+        self.tol_min_entry.set_range(0, 10000.0000)
         self.tol_min_entry.setSingleStep(0.1)
         self.tol_min_entry.setObjectName("gdb_tol_min")
 
@@ -258,7 +261,7 @@ class ToolsDB2UI:
         )
         self.tol_max_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.tol_max_entry.set_precision(self.decimals)
-        self.tol_max_entry.set_range(0, 9999.9999)
+        self.tol_max_entry.set_range(0, 10000.0000)
         self.tol_max_entry.setSingleStep(0.1)
         self.tol_max_entry.setObjectName("gdb_tol_max")
 
@@ -270,9 +273,9 @@ class ToolsDB2UI:
         self.tool_op_label.setToolTip(
             _("The kind of Application Tool where this tool is to be used."))
 
-        self.tool_op_combo = FCComboBox()
+        self.tool_op_combo = FCComboBox2()
         self.tool_op_combo.addItems(
-            [_("General"), _("Milling"), _("Drilling"), _('Isolation'), _('Paint'), _('NCC'), _("Cutout")])
+            [_("General"), _("Milling"), _("Drilling"), _('Isolation'), _('Paint'), _('NCC'), _('Cutout')])
         self.tool_op_combo.setObjectName('gdb_tool_target')
 
         self.grid_tool.addWidget(self.tool_op_label, 8, 0)
@@ -297,7 +300,7 @@ class ToolsDB2UI:
               "V = v-shape milling tool"))
 
         self.mill_shape_combo = FCComboBox()
-        self.mill_shape_combo.addItems(["C1", "C2", "C3", "C4", "B", "V"])
+        self.mill_shape_combo.addItems(self.tool_type_item_options)
         self.mill_shape_combo.setObjectName('gdb_shape')
 
         self.grid0.addWidget(self.shape_label, 2, 0)
@@ -310,7 +313,7 @@ class ToolsDB2UI:
               "Diameter of the tip for V-Shape Tools."))
 
         self.mill_vdia_entry = FCDoubleSpinner()
-        self.mill_vdia_entry.set_range(0.0000, 9999.9999)
+        self.mill_vdia_entry.set_range(0.0000, 10000.0000)
         self.mill_vdia_entry.set_precision(self.decimals)
         self.mill_vdia_entry.setObjectName('gdb_vdia')
 
@@ -346,7 +349,7 @@ class ToolsDB2UI:
               "Finish = finishing cut, high feedrate"))
 
         self.mill_type_combo = FCComboBox()
-        self.mill_type_combo.addItems(["Iso", "Rough", "Finish"])
+        self.mill_type_combo.addItems(self.type_item_options)
         self.mill_type_combo.setObjectName('gdb_type')
 
         self.grid0.addWidget(self.type_label, 10, 0)
@@ -363,7 +366,7 @@ class ToolsDB2UI:
               "Custom = custom offset using the Custom Offset value"))
 
         self.mill_tooloffset_combo = FCComboBox()
-        self.mill_tooloffset_combo.addItems(["Path", "In", "Out", "Custom"])
+        self.mill_tooloffset_combo.addItems(self.offset_item_options)
         self.mill_tooloffset_combo.setObjectName('gdb_tool_offset')
 
         self.grid0.addWidget(self.tooloffset_label, 12, 0)
@@ -376,7 +379,7 @@ class ToolsDB2UI:
               "A value to be used as offset from the current path."))
 
         self.mill_custom_offset_entry = FCDoubleSpinner()
-        self.mill_custom_offset_entry.set_range(-9999.9999, 9999.9999)
+        self.mill_custom_offset_entry.set_range(-10000.0000, 10000.0000)
         self.mill_custom_offset_entry.set_precision(self.decimals)
         self.mill_custom_offset_entry.setObjectName('gdb_custom_offset')
 
@@ -395,7 +398,7 @@ class ToolsDB2UI:
               "The depth at which to cut into material."))
 
         self.mill_cutz_entry = FCDoubleSpinner()
-        self.mill_cutz_entry.set_range(-9999.9999, 9999.9999)
+        self.mill_cutz_entry.set_range(-10000.0000, 10000.0000)
         self.mill_cutz_entry.set_precision(self.decimals)
         self.mill_cutz_entry.setObjectName('gdb_cutz')
 
@@ -422,7 +425,7 @@ class ToolsDB2UI:
               "The value used to cut into material on each pass."))
 
         self.mill_multidepth_entry = FCDoubleSpinner()
-        self.mill_multidepth_entry.set_range(-9999.9999, 9999.9999)
+        self.mill_multidepth_entry.set_range(-10000.0000, 10000.0000)
         self.mill_multidepth_entry.set_precision(self.decimals)
         self.mill_multidepth_entry.setObjectName('gdb_multidepth_entry')
 
@@ -437,7 +440,7 @@ class ToolsDB2UI:
               "above the surface of the material, avoiding all fixtures."))
 
         self.mill_travelz_entry = FCDoubleSpinner()
-        self.mill_travelz_entry.set_range(-9999.9999, 9999.9999)
+        self.mill_travelz_entry.set_range(-10000.0000, 10000.0000)
         self.mill_travelz_entry.set_precision(self.decimals)
         self.mill_travelz_entry.setObjectName('gdb_travelz')
 
@@ -470,7 +473,7 @@ class ToolsDB2UI:
               "the extra cut."))
 
         self.mill_ecut_length_entry = FCDoubleSpinner()
-        self.mill_ecut_length_entry.set_range(0.0000, 9999.9999)
+        self.mill_ecut_length_entry.set_range(0.0000, 10000.0000)
         self.mill_ecut_length_entry.set_precision(self.decimals)
         self.mill_ecut_length_entry.setObjectName('gdb_ecut_length')
 
@@ -489,7 +492,7 @@ class ToolsDB2UI:
               "The speed on XY plane used while cutting into material."))
 
         self.mill_frxy_entry = FCDoubleSpinner()
-        self.mill_frxy_entry.set_range(-999999.9999, 999999.9999)
+        self.mill_frxy_entry.set_range(-9910000.0000, 9910000.0000)
         self.mill_frxy_entry.set_precision(self.decimals)
         self.mill_frxy_entry.setObjectName('gdb_frxy')
 
@@ -503,7 +506,7 @@ class ToolsDB2UI:
               "The speed on Z plane."))
 
         self.mill_frz_entry = FCDoubleSpinner()
-        self.mill_frz_entry.set_range(-999999.9999, 999999.9999)
+        self.mill_frz_entry.set_range(-9910000.0000, 9910000.0000)
         self.mill_frz_entry.set_precision(self.decimals)
         self.mill_frz_entry.setObjectName('gdb_frz')
 
@@ -519,7 +522,7 @@ class ToolsDB2UI:
               "the G0 g-code command. Mostly 3D printers."))
 
         self.mill_frapids_entry = FCDoubleSpinner()
-        self.mill_frapids_entry.set_range(0.0000, 9999.9999)
+        self.mill_frapids_entry.set_range(0.0000, 10000.0000)
         self.mill_frapids_entry.set_precision(self.decimals)
         self.mill_frapids_entry.setObjectName('gdb_frapids')
 
@@ -539,7 +542,7 @@ class ToolsDB2UI:
               "The speed of the spindle in RPM."))
 
         self.mill_spindle_entry = FCDoubleSpinner()
-        self.mill_spindle_entry.set_range(-999999.9999, 999999.9999)
+        self.mill_spindle_entry.set_range(-9910000.0000, 9910000.0000)
         self.mill_spindle_entry.set_precision(self.decimals)
         self.mill_spindle_entry.setObjectName('gdb_spindle')
 
@@ -566,7 +569,7 @@ class ToolsDB2UI:
               "A delay used to allow the motor spindle reach its set speed."))
 
         self.mill_dwelltime_entry = FCDoubleSpinner()
-        self.mill_dwelltime_entry.set_range(0.0000, 9999.9999)
+        self.mill_dwelltime_entry.set_range(0.0000, 10000.0000)
         self.mill_dwelltime_entry.set_precision(self.decimals)
         self.mill_dwelltime_entry.setObjectName('gdb_dwelltime')
 
@@ -604,7 +607,7 @@ class ToolsDB2UI:
         # Milling Type Radio Button
         self.milling_type_label = FCLabel('%s:' % _('Milling Type'))
         self.milling_type_label.setToolTip(
-            _("Milling type when the selected tool is of type: 'iso_op':\n"
+            _("Milling type:\n"
               "- climb / best for precision milling and to reduce tool usage\n"
               "- conventional / useful when there is no backlash compensation")
         )
@@ -612,7 +615,7 @@ class ToolsDB2UI:
         self.ncc_milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
                                                 {'label': _('Conventional'), 'value': 'cv'}])
         self.ncc_milling_type_radio.setToolTip(
-            _("Milling type when the selected tool is of type: 'iso_op':\n"
+            _("Milling type:\n"
               "- climb / best for precision milling and to reduce tool usage\n"
               "- conventional / useful when there is no backlash compensation")
         )
@@ -626,8 +629,8 @@ class ToolsDB2UI:
         nccoverlabel.setToolTip(
             _("How much (percentage) of the tool width to overlap each tool pass.\n"
               "Adjust the value starting with lower values\n"
-              "and increasing it if areas that should be cleared are still \n"
-              "not cleared.\n"
+              "and increasing it if areas that should be processed are still \n"
+              "not processed.\n"
               "Lower values = faster processing, faster execution on CNC.\n"
               "Higher values = slow processing and slow execution on CNC\n"
               "due of too many paths.")
@@ -649,7 +652,7 @@ class ToolsDB2UI:
         )
         self.ncc_margin_entry = FCDoubleSpinner()
         self.ncc_margin_entry.set_precision(self.decimals)
-        self.ncc_margin_entry.set_range(-9999.9999, 9999.9999)
+        self.ncc_margin_entry.set_range(-10000.0000, 10000.0000)
         self.ncc_margin_entry.setObjectName("gdb_n_margin")
 
         self.grid2.addWidget(nccmarginlabel, 16, 0)
@@ -664,7 +667,7 @@ class ToolsDB2UI:
               "- Line-based: Parallel lines.")
         )
 
-        self.ncc_method_combo = FCComboBox()
+        self.ncc_method_combo = FCComboBox2()
         self.ncc_method_combo.addItems(
             [_("Standard"), _("Seed"), _("Lines"), _("Combo")]
         )
@@ -700,8 +703,7 @@ class ToolsDB2UI:
         self.ncc_choice_offset_cb.setToolTip(
             _("If used, it will add an offset to the copper features.\n"
               "The copper clearing will finish to a distance\n"
-              "from the copper features.\n"
-              "The value can be between 0 and 10 FlatCAM units.")
+              "from the copper features.")
         )
         self.grid2.addWidget(self.ncc_choice_offset_cb, 19, 0)
 
@@ -735,8 +737,8 @@ class ToolsDB2UI:
         ovlabel.setToolTip(
             _("How much (percentage) of the tool width to overlap each tool pass.\n"
               "Adjust the value starting with lower values\n"
-              "and increasing it if areas that should be painted are still \n"
-              "not painted.\n"
+              "and increasing it if areas that should be processed are still \n"
+              "not processed.\n"
               "Lower values = faster processing, faster execution on CNC.\n"
               "Higher values = slow processing and slow execution on CNC\n"
               "due of too many paths.")
@@ -760,7 +762,7 @@ class ToolsDB2UI:
         )
         self.paint_offset_entry = FCDoubleSpinner()
         self.paint_offset_entry.set_precision(self.decimals)
-        self.paint_offset_entry.set_range(-9999.9999, 9999.9999)
+        self.paint_offset_entry.set_range(-10000.0000, 10000.0000)
         self.paint_offset_entry.setObjectName('gdb_p_offset')
 
         self.grid3.addWidget(marginlabel, 2, 0)
@@ -779,7 +781,7 @@ class ToolsDB2UI:
               "in the order specified.")
         )
 
-        self.paint_method_combo = FCComboBox()
+        self.paint_method_combo = FCComboBox2()
         self.paint_method_combo.addItems(
             [_("Standard"), _("Seed"), _("Lines"), _("Laser_lines"), _("Combo")]
         )
@@ -850,7 +852,7 @@ class ToolsDB2UI:
         # Milling Type Radio Button
         self.iso_milling_type_label = FCLabel('%s:' % _('Milling Type'))
         self.iso_milling_type_label.setToolTip(
-            _("Milling type when the selected tool is of type: 'iso_op':\n"
+            _("Milling type:\n"
               "- climb / best for precision milling and to reduce tool usage\n"
               "- conventional / useful when there is no backlash compensation")
         )
@@ -858,7 +860,7 @@ class ToolsDB2UI:
         self.iso_milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
                                                 {'label': _('Conventional'), 'value': 'cv'}])
         self.iso_milling_type_radio.setToolTip(
-            _("Milling type when the selected tool is of type: 'iso_op':\n"
+            _("Milling type:\n"
               "- climb / best for precision milling and to reduce tool usage\n"
               "- conventional / useful when there is no backlash compensation")
         )
@@ -877,8 +879,8 @@ class ToolsDB2UI:
 
         self.iso_follow_cb = FCCheckBox()
         self.iso_follow_cb.setToolTip(_("Generate a 'Follow' geometry.\n"
-                                    "This means that it will cut through\n"
-                                    "the middle of the trace."))
+                                        "This means that it will cut through\n"
+                                        "the middle of the trace."))
         self.iso_follow_cb.setObjectName("gdb_i_follow")
 
         self.grid4.addWidget(self.follow_label, 6, 0)
@@ -924,9 +926,9 @@ class ToolsDB2UI:
         self.drill_cutz_entry.set_precision(self.decimals)
 
         if self.machinist_setting == 0:
-            self.drill_cutz_entry.set_range(-9999.9999, 0.0000)
+            self.drill_cutz_entry.set_range(-10000.0000, 0.0000)
         else:
-            self.drill_cutz_entry.set_range(-9999.9999, 9999.9999)
+            self.drill_cutz_entry.set_range(-10000.0000, 10000.0000)
 
         self.drill_cutz_entry.setSingleStep(0.1)
         self.drill_cutz_entry.setObjectName("gdb_e_cutz")
@@ -944,7 +946,7 @@ class ToolsDB2UI:
 
         self.drill_offset_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.drill_offset_entry.set_precision(self.decimals)
-        self.drill_offset_entry.set_range(-9999.9999, 9999.9999)
+        self.drill_offset_entry.set_range(-10000.0000, 10000.0000)
         self.drill_offset_entry.setObjectName("gdb_e_offset")
 
         self.grid5.addWidget(self.tool_offset_label, 6, 0)
@@ -973,7 +975,7 @@ class ToolsDB2UI:
               "The value used to cut into material on each pass."))
         self.drill_maxdepth_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.drill_maxdepth_entry.set_precision(self.decimals)
-        self.drill_maxdepth_entry.set_range(0, 9999.9999)
+        self.drill_maxdepth_entry.set_range(0, 10000.0000)
         self.drill_maxdepth_entry.setSingleStep(0.1)
 
         self.drill_maxdepth_entry.setToolTip(_("Depth of each pass (positive)."))
@@ -993,9 +995,9 @@ class ToolsDB2UI:
         self.drill_travelz_entry.set_precision(self.decimals)
 
         if self.machinist_setting == 0:
-            self.drill_travelz_entry.set_range(0.00001, 9999.9999)
+            self.drill_travelz_entry.set_range(0.00001, 10000.0000)
         else:
-            self.drill_travelz_entry.set_range(-9999.9999, 9999.9999)
+            self.drill_travelz_entry.set_range(-10000.0000, 10000.0000)
 
         self.drill_travelz_entry.setSingleStep(0.1)
         self.drill_travelz_entry.setObjectName("gdb_e_travelz")
@@ -1018,7 +1020,7 @@ class ToolsDB2UI:
         )
         self.drill_feedrate_z_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.drill_feedrate_z_entry.set_precision(self.decimals)
-        self.drill_feedrate_z_entry.set_range(0.0, 99999.9999)
+        self.drill_feedrate_z_entry.set_range(0.0, 910000.0000)
         self.drill_feedrate_z_entry.setSingleStep(0.1)
         self.drill_feedrate_z_entry.setObjectName("gdb_e_feedratez")
 
@@ -1036,7 +1038,7 @@ class ToolsDB2UI:
         )
         self.drill_feedrate_rapid_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.drill_feedrate_rapid_entry.set_precision(self.decimals)
-        self.drill_feedrate_rapid_entry.set_range(0.0, 99999.9999)
+        self.drill_feedrate_rapid_entry.set_range(0.0, 910000.0000)
         self.drill_feedrate_rapid_entry.setSingleStep(0.1)
         self.drill_feedrate_rapid_entry.setObjectName("gdb_e_fr_rapid")
 
@@ -1083,7 +1085,7 @@ class ToolsDB2UI:
               "A delay used to allow the motor spindle reach its set speed."))
         self.drill_dwelltime_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.drill_dwelltime_entry.set_precision(self.decimals)
-        self.drill_dwelltime_entry.set_range(0.0, 9999.9999)
+        self.drill_dwelltime_entry.set_range(0.0, 10000.0000)
         self.drill_dwelltime_entry.setSingleStep(0.1)
         self.drill_dwelltime_entry.setObjectName("gdb_e_dwelltime")
 
@@ -1146,7 +1148,7 @@ class ToolsDB2UI:
 
         # Margin
         self.cutout_margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.cutout_margin_entry.set_range(-9999.9999, 9999.9999)
+        self.cutout_margin_entry.set_range(-10000.0000, 10000.0000)
         self.cutout_margin_entry.setSingleStep(0.1)
         self.cutout_margin_entry.set_precision(self.decimals)
         self.cutout_margin_entry.setObjectName('gdb_ct_margin')
@@ -1208,9 +1210,9 @@ class ToolsDB2UI:
         self.cutout_thin_depth_entry.setObjectName('gdb_ct_gap_depth')
 
         if self.machinist_setting == 0:
-            self.cutout_thin_depth_entry.setRange(-9999.9999, -0.00001)
+            self.cutout_thin_depth_entry.setRange(-10000.0000, -0.00001)
         else:
-            self.cutout_thin_depth_entry.setRange(-9999.9999, 9999.9999)
+            self.cutout_thin_depth_entry.setRange(-10000.0000, 10000.0000)
         self.cutout_thin_depth_entry.setSingleStep(0.1)
 
         self.grid6.addWidget(self.thin_depth_label, 17, 0)
@@ -1394,19 +1396,16 @@ class ToolsDB2(QtWidgets.QWidget):
 
     mark_tools_rows = QtCore.pyqtSignal()
 
-    def __init__(self, app, callback_on_edited, callback_on_tool_request, parent=None):
+    def __init__(self, app, callback_on_tool_request, parent=None):
         super(ToolsDB2, self).__init__(parent)
 
         self.app = app
         self.app_ui = self.app.ui
         self.decimals = self.app.decimals
-        self.callback_app = callback_on_edited
 
         self.on_tool_request = callback_on_tool_request
 
-        self.offset_item_options = ["Path", "In", "Out", "Custom"]
-        self.type_item_options = ["Iso", "Rough", "Finish"]
-        self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"]
+        self.tools_db_changed_flag = False
 
         '''
         dict to hold all the tools in the Tools DB
@@ -1417,7 +1416,7 @@ class ToolsDB2(QtWidgets.QWidget):
                 'tooldia': self.app.defaults["geometry_cnctooldia"]
                 'offset': 'Path'
                 'offset_value': 0.0
-                'type':  _('Rough'),
+                'type':  'Rough',
                 'tool_type': 'C1'
                 'data': dict()
             }
@@ -1425,6 +1424,8 @@ class ToolsDB2(QtWidgets.QWidget):
         '''
         self.db_tool_dict = {}
 
+        self.old_color = QtGui.QColor('black')
+
         # ##############################################################################
         # ##############################################################################
         # TOOLS DATABASE UI
@@ -1719,7 +1720,13 @@ class ToolsDB2(QtWidgets.QWidget):
         self.ui_connect()
 
     def setup_db_ui(self):
-        filename = self.app.data_path + '\\tools_db.FlatDB'
+
+        # set the old color for the Tools Database Tab
+        for idx in range(self.app.ui.plot_tab_area.count()):
+            if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
+                self.old_color = self.app.ui.plot_tab_area.tabBar.tabTextColor(idx)
+
+        filename = self.app.tools_database_path()
 
         # load the database tools from the file
         try:
@@ -1727,7 +1734,7 @@ class ToolsDB2(QtWidgets.QWidget):
                 tools = f.read()
         except IOError:
             self.app.log.error("Could not load tools DB file.")
-            self.app.inform.emit('[ERROR] %s' % _("Could not load Tools DB file."))
+            self.app.inform.emit('[ERROR] %s' % _("Could not load the file."))
             return
 
         try:
@@ -1811,7 +1818,7 @@ class ToolsDB2(QtWidgets.QWidget):
 
         self.ui.tool_description_box.setEnabled(True)
         if self.db_tool_dict:
-            if tool_target == _("General"):
+            if tool_target == 0:    # _("General")
                 self.ui.milling_box.setEnabled(True)
                 self.ui.ncc_box.setEnabled(True)
                 self.ui.paint_box.setEnabled(True)
@@ -1833,33 +1840,33 @@ class ToolsDB2(QtWidgets.QWidget):
                 self.ui.drill_box.hide()
                 self.ui.cutout_box.hide()
 
-                if tool_target == _("Milling"):
+                if tool_target == 1:    # _("Milling")
                     self.ui.milling_box.setEnabled(True)
                     self.ui.milling_box.show()
 
-                if tool_target == _("Drilling"):
+                if tool_target == 2:    # _("Drilling")
                     self.ui.drill_box.setEnabled(True)
                     self.ui.drill_box.show()
 
-                if tool_target == _("Isolation"):
+                if tool_target == 3:    # _("Isolation")
                     self.ui.iso_box.setEnabled(True)
                     self.ui.iso_box.show()
                     self.ui.milling_box.setEnabled(True)
                     self.ui.milling_box.show()
 
-                if tool_target == _("Paint"):
+                if tool_target == 4:    # _("Paint")
                     self.ui.paint_box.setEnabled(True)
                     self.ui.paint_box.show()
                     self.ui.milling_box.setEnabled(True)
                     self.ui.milling_box.show()
 
-                if tool_target == _("NCC"):
+                if tool_target == 5:    # _("NCC")
                     self.ui.ncc_box.setEnabled(True)
                     self.ui.ncc_box.show()
                     self.ui.milling_box.setEnabled(True)
                     self.ui.milling_box.show()
 
-                if tool_target == _("Cutout"):
+                if tool_target == 6:    # _("Cutout")
                     self.ui.cutout_box.setEnabled(True)
                     self.ui.cutout_box.show()
                     self.ui.milling_box.setEnabled(True)
@@ -1874,7 +1881,7 @@ class ToolsDB2(QtWidgets.QWidget):
         default_data = {}
         default_data.update({
             "plot":             True,
-            "tool_target": _("General"),
+            "tool_target": 0,   # _("General")
             "tol_min": 0.0,
             "tol_max": 0.0,
 
@@ -1899,6 +1906,28 @@ class ToolsDB2(QtWidgets.QWidget):
             "toolchangez":      float(self.app.defaults["geometry_toolchangez"]),
             "startz":           self.app.defaults["geometry_startz"],
             "endz":             float(self.app.defaults["geometry_endz"]),
+            "endxy":            self.app.defaults["geometry_endxy"],
+            "search_time":      int(self.app.defaults["geometry_search_time"]),
+            "z_pdepth":         float(self.app.defaults["geometry_z_pdepth"]),
+            "f_plunge":         float(self.app.defaults["geometry_f_plunge"]),
+
+            "spindledir":               self.app.defaults["geometry_spindledir"],
+            "optimization_type":        self.app.defaults["geometry_optimization_type"],
+            "feedrate_probe":           self.app.defaults["geometry_feedrate_probe"],
+
+            "segx":             self.app.defaults["geometry_segx"],
+            "segy":             self.app.defaults["geometry_segy"],
+            "area_exclusion":   self.app.defaults["geometry_area_exclusion"],
+            "area_shape":       self.app.defaults["geometry_area_shape"],
+            "area_strategy":    self.app.defaults["geometry_area_strategy"],
+            "area_overz":       self.app.defaults["geometry_area_overz"],
+            "polish":           self.app.defaults["geometry_polish"],
+            "polish_dia":       self.app.defaults["geometry_polish_dia"],
+            "polish_pressure":  self.app.defaults["geometry_polish_pressure"],
+            "polish_travelz":   self.app.defaults["geometry_polish_travelz"],
+            "polish_margin":    self.app.defaults["geometry_polish_margin"],
+            "polish_overlap":   self.app.defaults["geometry_polish_overlap"],
+            "polish_method":    self.app.defaults["geometry_polish_method"],
 
             # NCC
             "tools_ncc_operation":       self.app.defaults["tools_ncc_operation"],
@@ -1963,8 +1992,7 @@ class ToolsDB2(QtWidgets.QWidget):
         else:
             new_name = "new_tool_1"
 
-        dict_elem = {}
-        dict_elem['name'] = new_name
+        dict_elem = {'name': new_name}
         if type(self.app.defaults["geometry_cnctooldia"]) == float:
             dict_elem['tooldia'] = self.app.defaults["geometry_cnctooldia"]
         else:
@@ -2029,7 +2057,7 @@ class ToolsDB2(QtWidgets.QWidget):
             self.ui.tree_widget.setCurrentItem(last_item)
             last_item.setSelected(True)
 
-        self.callback_app()
+        self.on_tools_db_edited()
         self.app.inform.emit('[success] %s' % _("Tool copied from Tools DB."))
 
     def on_tool_delete(self):
@@ -2068,12 +2096,13 @@ class ToolsDB2(QtWidgets.QWidget):
         date = date.replace(' ', '_')
 
         filter__ = "Text File (*.TXT);;All Files (*.*)"
-        filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Tools Database"),
-                                                           directory='{l_save}/FlatCAM_{n}_{date}'.format(
-                                                                l_save=str(self.app.get_last_save_folder()),
-                                                                n=_("Tools_Database"),
-                                                                date=date),
-                                                           ext_filter=filter__)
+
+        filename, _f = FCFileSaveDialog.get_saved_filename(
+            caption=_("Export Tools Database"),
+            directory='{l_save}/FlatCAM_{n}_{date}'.format(l_save=str(self.app.get_last_save_folder()),
+                                                           n=_("Tools_Database"),
+                                                           date=date),
+            ext_filter=filter__)
 
         filename = str(filename)
 
@@ -2097,7 +2126,7 @@ class ToolsDB2(QtWidgets.QWidget):
                 e = sys.exc_info()[0]
                 self.app.log.error("Could not load Tools DB file.")
                 self.app.log.error(str(e))
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load Tools DB file."))
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load the file."))
                 return
 
             # Save update options
@@ -2131,7 +2160,7 @@ class ToolsDB2(QtWidgets.QWidget):
                     tools_in_db = f.read()
             except IOError:
                 self.app.log.error("Could not load Tools DB file.")
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load Tools DB file."))
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load the file."))
                 return
 
             try:
@@ -2149,12 +2178,12 @@ class ToolsDB2(QtWidgets.QWidget):
     def on_save_tools_db(self, silent=False):
         self.app.log.debug("ToolsDB.on_save_button() --> Saving Tools Database to file.")
 
-        filename = self.app.data_path + "/tools_db.FlatDB"
+        filename = self.app.tools_database_path()
 
         # Preferences save, update the color of the Tools DB Tab text
         for idx in range(self.app_ui.plot_tab_area.count()):
             if self.app_ui.plot_tab_area.tabText(idx) == _("Tools Database"):
-                self.app_ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('black'))
+                self.app_ui.plot_tab_area.tabBar.setTabTextColor(idx, self.old_color)
                 self.ui.save_db_btn.setStyleSheet("")
 
                 # clean the dictionary and leave only keys of interest
@@ -2170,7 +2199,7 @@ class ToolsDB2(QtWidgets.QWidget):
                     if self.db_tool_dict[tool_id]['data']['tool_target'] == _('Drilling'):
                         for k in list(self.db_tool_dict[tool_id]['data'].keys()):
                             if str(k).startswith('tools_'):
-                                if str(k).startswith('tools_drill'):
+                                if str(k).startswith('tools_drill') or str(k).startswith('tools_mill'):
                                     pass
                                 else:
                                     self.db_tool_dict[tool_id]['data'].pop(k, None)
@@ -2178,7 +2207,7 @@ class ToolsDB2(QtWidgets.QWidget):
                     if self.db_tool_dict[tool_id]['data']['tool_target'] == _('Isolation'):
                         for k in list(self.db_tool_dict[tool_id]['data'].keys()):
                             if str(k).startswith('tools_'):
-                                if str(k).startswith('tools_iso'):
+                                if str(k).startswith('tools_iso') or str(k).startswith('tools_mill'):
                                     pass
                                 else:
                                     self.db_tool_dict[tool_id]['data'].pop(k, None)
@@ -2186,7 +2215,7 @@ class ToolsDB2(QtWidgets.QWidget):
                     if self.db_tool_dict[tool_id]['data']['tool_target'] == _('Paint'):
                         for k in list(self.db_tool_dict[tool_id]['data'].keys()):
                             if str(k).startswith('tools_'):
-                                if str(k).startswith('tools_paint'):
+                                if str(k).startswith('tools_paint') or str(k).startswith('tools_mill'):
                                     pass
                                 else:
                                     self.db_tool_dict[tool_id]['data'].pop(k, None)
@@ -2194,7 +2223,15 @@ class ToolsDB2(QtWidgets.QWidget):
                     if self.db_tool_dict[tool_id]['data']['tool_target'] == _('NCC'):
                         for k in list(self.db_tool_dict[tool_id]['data'].keys()):
                             if str(k).startswith('tools_'):
-                                if str(k).startswith('tools_ncc'):
+                                if str(k).startswith('tools_ncc') or str(k).startswith('tools_mill'):
+                                    pass
+                                else:
+                                    self.db_tool_dict[tool_id]['data'].pop(k, None)
+
+                    if self.db_tool_dict[tool_id]['data']['tool_target'] == _('Cutout'):
+                        for k in list(self.db_tool_dict[tool_id]['data'].keys()):
+                            if str(k).startswith('tools_'):
+                                if str(k).startswith('tools_cutout') or str(k).startswith('tools_mill'):
                                     pass
                                 else:
                                     self.db_tool_dict[tool_id]['data'].pop(k, None)
@@ -2355,11 +2392,24 @@ class ToolsDB2(QtWidgets.QWidget):
             if wdg is None:
                 return
 
+            if isinstance(wdg, FCButton) or isinstance(wdg, QtWidgets.QAction):
+                # this is called when adding a new tool; no need to run the update below since that section is for
+                # when editing a tool
+                self.on_tools_db_edited()
+                return
+
             wdg_name = wdg.objectName()
             val = wdg.get_value()
-        except AttributeError:
+        except AttributeError as err:
+            self.app.log.debug("ToolsDB2.update_storage() -> %s" % str(err))
             return
 
+        # #############################################################################################################
+        # #############################################################################################################
+        # ################ EDITING PARAMETERS IN A TOOL SECTION
+        # #############################################################################################################
+        # #############################################################################################################
+
         # #############################################################################################################
         # this might change in the future; it makes sense to change values at once for all tools
         # for now change values only for one tool at once
@@ -2523,7 +2573,7 @@ class ToolsDB2(QtWidgets.QWidget):
             elif wdg_name == "gdb_ct_mb_spacing":
                 self.db_tool_dict[tool_id]['data']['tools_cutout_mb_spacing'] = val
 
-        self.callback_app()
+        self.on_tools_db_edited()
 
     def on_tool_requested_from_app(self):
         if not self.ui.tree_widget.selectedItems():
@@ -2542,6 +2592,25 @@ class ToolsDB2(QtWidgets.QWidget):
                     selected_tool = self.db_tool_dict[key]
                     self.on_tool_request(tool=selected_tool)
 
+    def on_tools_db_edited(self, silent=None):
+        """
+        Executed whenever a tool is edited in Tools Database.
+        Will color the text of the Tools Database tab to Red color.
+
+        :return:
+        """
+
+        for idx in range(self.app.ui.plot_tab_area.count()):
+            if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
+                self.app.ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('red'))
+
+        self.ui.save_db_btn.setStyleSheet("QPushButton {color: red;}")
+
+        self.tools_db_changed_flag = True
+        if silent is None:
+            msg = '[WARNING_NOTCL] %s' % _("Tools in Tools Database edited but not saved.")
+            self.app.inform[str, bool].emit(msg, False)
+
     def on_cancel_tool(self):
         for idx in range(self.app_ui.plot_tab_area.count()):
             if self.app_ui.plot_tab_area.tabText(idx) == _("Tools Database"):
@@ -2594,7 +2663,7 @@ class ToolsDB2(QtWidgets.QWidget):
 #                 'tooldia': self.app.defaults["geometry_cnctooldia"]
 #                 'offset': 'Path'
 #                 'offset_value': 0.0
-#                 'type':  _('Rough'),
+#                 'type':  'Rough',
 #                 'tool_type': 'C1'
 #                 'data': dict()
 #             }
@@ -2845,7 +2914,7 @@ class ToolsDB2(QtWidgets.QWidget):
 #               "A position on Z plane to move immediately after job stop."))
 #
 #     def setup_db_ui(self):
-#         filename = self.app.data_path + '/tools_db.FlatDB'
+#         filename = self.app.tools_database_path()
 #
 #         # load the database tools from the file
 #         try:
@@ -2928,7 +2997,7 @@ class ToolsDB2(QtWidgets.QWidget):
 #         dia_item = FCDoubleSpinner()
 #         dia_item.set_precision(self.decimals)
 #         dia_item.setSingleStep(0.1)
-#         dia_item.set_range(0.0, 9999.9999)
+#         dia_item.set_range(0.0, 10000.0000)
 #         dia_item.set_value(float(tooldict['tooldia']))
 #         widget.setCellWidget(row, 2, dia_item)
 #
@@ -2941,7 +3010,7 @@ class ToolsDB2(QtWidgets.QWidget):
 #         c_offset_item = FCDoubleSpinner()
 #         c_offset_item.set_precision(self.decimals)
 #         c_offset_item.setSingleStep(0.1)
-#         c_offset_item.set_range(-9999.9999, 9999.9999)
+#         c_offset_item.set_range(-10000.0000, 10000.0000)
 #         c_offset_item.set_value(float(tooldict['offset_value']))
 #         widget.setCellWidget(row, 4, c_offset_item)
 #
@@ -2961,9 +3030,9 @@ class ToolsDB2(QtWidgets.QWidget):
 #         cutz_item.set_precision(self.decimals)
 #         cutz_item.setSingleStep(0.1)
 #         if self.app.defaults['global_machinist_setting']:
-#             cutz_item.set_range(-9999.9999, 9999.9999)
+#             cutz_item.set_range(-10000.0000, 10000.0000)
 #         else:
-#             cutz_item.set_range(-9999.9999, -0.0000)
+#             cutz_item.set_range(-10000.0000, -0.0000)
 #
 #         cutz_item.set_value(float(data['cutz']))
 #         widget.setCellWidget(row, 7, cutz_item)
@@ -2985,14 +3054,14 @@ class ToolsDB2(QtWidgets.QWidget):
 #         depth_per_pass_item = FCDoubleSpinner()
 #         depth_per_pass_item.set_precision(self.decimals)
 #         depth_per_pass_item.setSingleStep(0.1)
-#         depth_per_pass_item.set_range(0.0, 9999.9999)
+#         depth_per_pass_item.set_range(0.0, 10000.0000)
 #         depth_per_pass_item.set_value(float(data['depthperpass']))
 #         widget.setCellWidget(row, 9, depth_per_pass_item)
 #
 #         vtip_dia_item = FCDoubleSpinner()
 #         vtip_dia_item.set_precision(self.decimals)
 #         vtip_dia_item.setSingleStep(0.1)
-#         vtip_dia_item.set_range(0.0, 9999.9999)
+#         vtip_dia_item.set_range(0.0, 10000.0000)
 #         vtip_dia_item.set_value(float(data['vtipdia']))
 #         widget.setCellWidget(row, 10, vtip_dia_item)
 #
@@ -3007,28 +3076,28 @@ class ToolsDB2(QtWidgets.QWidget):
 #         travelz_item.set_precision(self.decimals)
 #         travelz_item.setSingleStep(0.1)
 #         if self.app.defaults['global_machinist_setting']:
-#             travelz_item.set_range(-9999.9999, 9999.9999)
+#             travelz_item.set_range(-10000.0000, 10000.0000)
 #         else:
-#             travelz_item.set_range(0.0000, 9999.9999)
+#             travelz_item.set_range(0.0000, 10000.0000)
 #
 #         travelz_item.set_value(float(data['travelz']))
 #         widget.setCellWidget(row, 12, travelz_item)
 #
 #         fr_item = FCDoubleSpinner()
 #         fr_item.set_precision(self.decimals)
-#         fr_item.set_range(0.0, 9999.9999)
+#         fr_item.set_range(0.0, 10000.0000)
 #         fr_item.set_value(float(data['feedrate']))
 #         widget.setCellWidget(row, 13, fr_item)
 #
 #         frz_item = FCDoubleSpinner()
 #         frz_item.set_precision(self.decimals)
-#         frz_item.set_range(0.0, 9999.9999)
+#         frz_item.set_range(0.0, 10000.0000)
 #         frz_item.set_value(float(data['feedrate_z']))
 #         widget.setCellWidget(row, 14, frz_item)
 #
 #         frrapids_item = FCDoubleSpinner()
 #         frrapids_item.set_precision(self.decimals)
-#         frrapids_item.set_range(0.0, 9999.9999)
+#         frrapids_item.set_range(0.0, 10000.0000)
 #         frrapids_item.set_value(float(data['feedrate_rapid']))
 #         widget.setCellWidget(row, 15, frrapids_item)
 #
@@ -3044,7 +3113,7 @@ class ToolsDB2(QtWidgets.QWidget):
 #
 #         dwelltime_item = FCDoubleSpinner()
 #         dwelltime_item.set_precision(self.decimals)
-#         dwelltime_item.set_range(0.0000, 9999.9999)
+#         dwelltime_item.set_range(0.0000, 10000.0000)
 #         dwelltime_item.set_value(float(data['dwelltime']))
 #         widget.setCellWidget(row, 18, dwelltime_item)
 #
@@ -3060,7 +3129,7 @@ class ToolsDB2(QtWidgets.QWidget):
 #
 #         ecut_length_item = FCDoubleSpinner()
 #         ecut_length_item.set_precision(self.decimals)
-#         ecut_length_item.set_range(0.0000, 9999.9999)
+#         ecut_length_item.set_range(0.0000, 10000.0000)
 #         ecut_length_item.set_value(data['extracut_length'])
 #         widget.setCellWidget(row, 21, ecut_length_item)
 #
@@ -3075,9 +3144,9 @@ class ToolsDB2(QtWidgets.QWidget):
 #         toolchangez_item.set_precision(self.decimals)
 #         toolchangez_item.setSingleStep(0.1)
 #         if self.app.defaults['global_machinist_setting']:
-#             toolchangez_item.set_range(-9999.9999, 9999.9999)
+#             toolchangez_item.set_range(-10000.0000, 10000.0000)
 #         else:
-#             toolchangez_item.set_range(0.0000, 9999.9999)
+#             toolchangez_item.set_range(0.0000, 10000.0000)
 #
 #         toolchangez_item.set_value(float(data['toolchangez']))
 #         widget.setCellWidget(row, 24, toolchangez_item)
@@ -3089,9 +3158,9 @@ class ToolsDB2(QtWidgets.QWidget):
 #         endz_item.set_precision(self.decimals)
 #         endz_item.setSingleStep(0.1)
 #         if self.app.defaults['global_machinist_setting']:
-#             endz_item.set_range(-9999.9999, 9999.9999)
+#             endz_item.set_range(-10000.0000, 10000.0000)
 #         else:
-#             endz_item.set_range(0.0000, 9999.9999)
+#             endz_item.set_range(0.0000, 10000.0000)
 #
 #         endz_item.set_value(float(data['endz']))
 #         widget.setCellWidget(row, 26, endz_item)
@@ -3282,7 +3351,7 @@ class ToolsDB2(QtWidgets.QWidget):
 #     def on_save_tools_db(self, silent=False):
 #         self.app.log.debug("ToolsDB.on_save_button() --> Saving Tools Database to file.")
 #
-#         filename = self.app.data_path + "/tools_db.FlatDB"
+#         filename = self.app.tools_database_path()
 #
 #         # Preferences save, update the color of the Tools DB Tab text
 #         for idx in range(self.app_ui.plot_tab_area.count()):

File diff suppressed because it is too large
+ 506 - 120
appEditors/AppExcEditor.py


+ 145 - 137
appEditors/AppGeoEditor.py

@@ -12,21 +12,22 @@
 # ######################################################### ##
 
 from PyQt5 import QtGui, QtCore, QtWidgets
-from PyQt5.QtCore import Qt, QSettings
+from PyQt5.QtCore import Qt
 
 from camlib import distance, arc, three_point_circle, Geometry, FlatCAMRTreeStorage
 from appTool import AppTool
 from appGUI.GUIElements import OptionalInputSection, FCCheckBox, FCLabel, FCComboBox, FCTextAreaRich, \
-    FCDoubleSpinner, FCButton, FCInputDialog, FCTree, NumericalEvalTupleEntry
+    FCDoubleSpinner, FCButton, FCInputDoubleSpinner, FCTree, NumericalEvalTupleEntry
 from appParsers.ParseFont import *
 
-from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon
+from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon, Point
 from shapely.ops import unary_union, linemerge
 import shapely.affinity as affinity
 from shapely.geometry.polygon import orient
 
 import numpy as np
 from numpy.linalg import norm as numpy_norm
+import logging
 
 from rtree import index as rtindex
 
@@ -40,6 +41,8 @@ fcTranslate.apply_language('strings')
 if '_' not in builtins.__dict__:
     _ = gettext.gettext
 
+log = logging.getLogger('base')
+
 
 class BufferSelectionTool(AppTool):
     """
@@ -80,9 +83,9 @@ class BufferSelectionTool(AppTool):
         # Buffer distance
         self.buffer_distance_entry = FCDoubleSpinner()
         self.buffer_distance_entry.set_precision(self.decimals)
-        self.buffer_distance_entry.set_range(0.0000, 999999.9999)
-        form_layout.addRow(_("Buffer distance:"), self.buffer_distance_entry)
-        self.buffer_corner_lbl = FCLabel(_("Buffer corner:"))
+        self.buffer_distance_entry.set_range(0.0000, 9910000.0000)
+        form_layout.addRow('%S:' % _("Buffer distance"), self.buffer_distance_entry)
+        self.buffer_corner_lbl = FCLabel('%s:' % _("Buffer corner"))
         self.buffer_corner_lbl.setToolTip(
             _("There are 3 types of corners:\n"
               " - 'Round': the corner is rounded for exterior buffer.\n"
@@ -435,14 +438,14 @@ class PaintOptionsTool(AppTool):
         grid.setColumnStretch(1, 1)
 
         # Tool dia
-        ptdlabel = FCLabel('%s:' % _('Tool dia'))
+        ptdlabel = FCLabel('%s:' % _('Tool Dia'))
         ptdlabel.setToolTip(
            _("Diameter of the tool to be used in the operation.")
         )
         grid.addWidget(ptdlabel, 0, 0)
 
         self.painttooldia_entry = FCDoubleSpinner()
-        self.painttooldia_entry.set_range(-9999.9999, 9999.9999)
+        self.painttooldia_entry.set_range(-10000.0000, 10000.0000)
         self.painttooldia_entry.set_precision(self.decimals)
         grid.addWidget(self.painttooldia_entry, 0, 1)
 
@@ -451,8 +454,8 @@ class PaintOptionsTool(AppTool):
         ovlabel.setToolTip(
             _("How much (percentage) of the tool width to overlap each tool pass.\n"
               "Adjust the value starting with lower values\n"
-              "and increasing it if areas that should be painted are still \n"
-              "not painted.\n"
+              "and increasing it if areas that should be processed are still \n"
+              "not processed.\n"
               "Lower values = faster processing, faster execution on CNC.\n"
               "Higher values = slow processing and slow execution on CNC\n"
               "due of too many paths.")
@@ -474,7 +477,7 @@ class PaintOptionsTool(AppTool):
              "be painted.")
         )
         self.paintmargin_entry = FCDoubleSpinner()
-        self.paintmargin_entry.set_range(-9999.9999, 9999.9999)
+        self.paintmargin_entry.set_range(-10000.0000, 10000.0000)
         self.paintmargin_entry.set_precision(self.decimals)
 
         grid.addWidget(marginlabel, 2, 0)
@@ -579,7 +582,7 @@ class PaintOptionsTool(AppTool):
 
     def on_paint(self):
         if not self.fcdraw.selected:
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. No shape selected."))
+            self.app.inform.emit('[WARNING_NOTCL] %s %s' % (_("Cancelled."), _("No shape selected.")))
             return
 
         tooldia = self.painttooldia_entry.get_value()
@@ -682,7 +685,7 @@ class TransformEditorTool(AppTool):
 
         self.rotate_label = FCLabel('%s:' % _("Angle"))
         self.rotate_label.setToolTip(
-            _("Angle for Rotation action, in degrees.\n"
+            _("Angle, in degrees.\n"
               "Float number between -360 and 359.\n"
               "Positive numbers for CW motion.\n"
               "Negative numbers for CCW motion.")
@@ -939,7 +942,7 @@ class TransformEditorTool(AppTool):
         self.buffer_entry.set_precision(self.decimals)
         self.buffer_entry.setSingleStep(0.1)
         self.buffer_entry.setWrapping(True)
-        self.buffer_entry.set_range(-9999.9999, 9999.9999)
+        self.buffer_entry.set_range(-10000.0000, 10000.0000)
 
         self.buffer_button = FCButton(_("Buffer D"))
         self.buffer_button.setToolTip(
@@ -1068,7 +1071,7 @@ class TransformEditorTool(AppTool):
 
     def template(self):
         if not self.draw_app.selected:
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. No shape selected."))
+            self.app.inform.emit('[WARNING_NOTCL] %s %s' % (_("Cancelled."), _("No shape selected.")))
             return
 
         self.draw_app.select_tool("select")
@@ -1263,7 +1266,7 @@ class TransformEditorTool(AppTool):
         """
         Rotate geometry
 
-        :param num:     Rotate with a known angle value, val
+        :param val:     Rotate with a known angle value, val
         :param point:   Reference point for rotation: tuple
         :return:
         """
@@ -1281,9 +1284,9 @@ class TransformEditorTool(AppTool):
                     sel_sha.rotate(-val, point=(px, py))
                     self.draw_app.replot()
 
-                self.app.inform.emit('[success] %s' % _("Done. Rotate completed."))
+                self.app.inform.emit('[success] %s' % _("Done."))
             except Exception as e:
-                self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Rotation action was not executed"), str(e)))
+                self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed"), str(e)))
                 return
 
     def on_flip(self, axis, point):
@@ -1309,20 +1312,21 @@ class TransformEditorTool(AppTool):
                 for sha in shape_list:
                     if axis == 'X':
                         sha.mirror('X', (px, py))
-                        self.app.inform.emit('[success] %s...' % _('Flip on the Y axis done'))
+                        self.app.inform.emit('[success] %s...' % _('Flip on Y axis done'))
                     elif axis == 'Y':
                         sha.mirror('Y', (px, py))
-                        self.app.inform.emit('[success] %s' % _('Flip on the X axis done'))
+                        self.app.inform.emit('[success] %s' % _('Flip on X axis done'))
                     self.draw_app.replot()
 
             except Exception as e:
-                self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Flip action was not executed"), str(e)))
+                self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed"), str(e)))
                 return
 
     def on_skew(self, axis, xval, yval, point):
         """
         Skew geometry
 
+        :param point:
         :param axis:    Axis on which to deform, skew
         :param xval:    Skew value on X axis
         :param yval:    Skew value on Y axis
@@ -1349,7 +1353,7 @@ class TransformEditorTool(AppTool):
                     self.app.inform.emit('[success] %s...' % _('Skew on the Y axis done'))
 
             except Exception as e:
-                self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Skew action was not executed"), str(e)))
+                self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed"), str(e)))
                 return
 
     def on_scale(self, axis, xfactor, yfactor, point=None):
@@ -1383,7 +1387,7 @@ class TransformEditorTool(AppTool):
                 else:
                     self.app.inform.emit('[success] %s...' % _('Scale on the Y axis done'))
             except Exception as e:
-                self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Scale action was not executed"), str(e)))
+                self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed"), str(e)))
                 return
 
     def on_offset(self, axis, num):
@@ -1416,14 +1420,14 @@ class TransformEditorTool(AppTool):
                     self.app.inform.emit('[success] %s...' % _('Offset on the Y axis done'))
 
             except Exception as e:
-                self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Offset action was not executed"), str(e)))
+                self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed"), str(e)))
                 return
 
     def on_buffer_action(self, value, join, factor=None):
         shape_list = self.draw_app.selected
 
         if not shape_list:
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("No shape selected"))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("No shape selected."))
             return
         else:
             with self.app.proc_container.new(_("Applying Buffer")):
@@ -1437,87 +1441,92 @@ class TransformEditorTool(AppTool):
 
                 except Exception as e:
                     self.app.log.debug("TransformEditorTool.on_buffer_action() --> %s" % str(e))
-                    self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed, due of"), str(e)))
+                    self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed"), str(e)))
                     return
 
     def on_rotate_key(self):
-        val_box = FCInputDialog(title=_("Rotate ..."),
-                                text='%s:' % _('Enter an Angle Value (degrees)'),
-                                min=-359.9999, max=360.0000, decimals=self.decimals,
-                                init_val=float(self.app.defaults['tools_transform_rotate']))
-        val_box.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/rotate.png'))
+        val_box = FCInputDoubleSpinner(title=_("Rotate ..."),
+                                       text='%s:' % _('Enter an Angle Value (degrees)'),
+                                       min=-359.9999, max=360.0000, decimals=self.decimals,
+                                       init_val=float(self.app.defaults['tools_transform_rotate']),
+                                       parent=self.app.ui)
+        val_box.set_icon(QtGui.QIcon(self.app.resource_location + '/rotate.png'))
 
         val, ok = val_box.get_value()
         if ok:
             self.on_rotate(val=val, ref=1)
-            self.app.inform.emit('[success] %s...' % _("Geometry shape rotate done"))
+            self.app.inform.emit('[success] %s...' % _("Rotate done"))
             return
         else:
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Geometry shape rotate cancelled"))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Rotate cancelled"))
 
     def on_offx_key(self):
         units = self.app.defaults['units'].lower()
 
-        val_box = FCInputDialog(title=_("Offset on X axis ..."),
-                                text='%s: (%s)' % (_('Enter a distance Value'), str(units)),
-                                min=-9999.9999, max=10000.0000, decimals=self.decimals,
-                                init_val=float(self.app.defaults['tools_transform_offset_x']))
-        val_box.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/offsetx32.png'))
+        val_box = FCInputDoubleSpinner(title=_("Offset on X axis ..."),
+                                       text='%s: (%s)' % (_('Enter a distance Value'), str(units)),
+                                       min=-10000.0000, max=10000.0000, decimals=self.decimals,
+                                       init_val=float(self.app.defaults['tools_transform_offset_x']),
+                                       parent=self.app.ui)
+        val_box.set_icon(QtGui.QIcon(self.app.resource_location + '/offsetx32.png'))
 
         val, ok = val_box.get_value()
         if ok:
             self.on_offx(val=val)
-            self.app.inform.emit('[success] %s' % _("Geometry shape offset on X axis done"))
+            self.app.inform.emit('[success] %s' % _("Offset on the X axis done"))
             return
         else:
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Geometry shape offset X cancelled"))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Offset X cancelled"))
 
     def on_offy_key(self):
         units = self.app.defaults['units'].lower()
 
-        val_box = FCInputDialog(title=_("Offset on Y axis ..."),
-                                text='%s: (%s)' % (_('Enter a distance Value'), str(units)),
-                                min=-9999.9999, max=10000.0000, decimals=self.decimals,
-                                init_val=float(self.app.defaults['tools_transform_offset_y']))
-        val_box.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/offsety32.png'))
+        val_box = FCInputDoubleSpinner(title=_("Offset on Y axis ..."),
+                                       text='%s: (%s)' % (_('Enter a distance Value'), str(units)),
+                                       min=-10000.0000, max=10000.0000, decimals=self.decimals,
+                                       init_val=float(self.app.defaults['tools_transform_offset_y']),
+                                       parent=self.app.ui)
+        val_box.set_icon(QtGui.QIcon(self.app.resource_location + '/offsety32.png'))
 
         val, ok = val_box.get_value()
         if ok:
             self.on_offx(val=val)
-            self.app.inform.emit('[success] %s...' % _("Geometry shape offset on Y axis done"))
+            self.app.inform.emit('[success] %s...' % _("Offset on Y axis done"))
             return
         else:
-            self.app.inform.emit('[success] %s...' % _("Geometry shape offset on Y axis canceled"))
+            self.app.inform.emit('[success] %s...' % _("Offset on the Y axis canceled"))
 
     def on_skewx_key(self):
-        val_box = FCInputDialog(title=_("Skew on X axis ..."),
-                                text='%s:' % _('Enter an Angle Value (degrees)'),
-                                min=-359.9999, max=360.0000, decimals=self.decimals,
-                                init_val=float(self.app.defaults['tools_transform_skew_x']))
-        val_box.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/skewX.png'))
+        val_box = FCInputDoubleSpinner(title=_("Skew on X axis ..."),
+                                       text='%s:' % _('Enter an Angle Value (degrees)'),
+                                       min=-359.9999, max=360.0000, decimals=self.decimals,
+                                       init_val=float(self.app.defaults['tools_transform_skew_x']),
+                                       parent=self.app.ui)
+        val_box.set_icon(QtGui.QIcon(self.app.resource_location + '/skewX.png'))
 
         val, ok = val_box.get_value()
         if ok:
             self.on_skewx(val=val, ref=3)
-            self.app.inform.emit('[success] %s...' % _("Geometry shape skew on X axis done"))
+            self.app.inform.emit('[success] %s...' % _("Skew on X axis done"))
             return
         else:
-            self.app.inform.emit('[success] %s...' % _("Geometry shape skew on X axis canceled"))
+            self.app.inform.emit('[success] %s...' % _("Skew on X axis canceled"))
 
     def on_skewy_key(self):
-        val_box = FCInputDialog(title=_("Skew on Y axis ..."),
-                                text='%s:' % _('Enter an Angle Value (degrees)'),
-                                min=-359.9999, max=360.0000, decimals=self.decimals,
-                                init_val=float(self.app.defaults['tools_transform_skew_y']))
-        val_box.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/skewY.png'))
+        val_box = FCInputDoubleSpinner(title=_("Skew on Y axis ..."),
+                                       text='%s:' % _('Enter an Angle Value (degrees)'),
+                                       min=-359.9999, max=360.0000, decimals=self.decimals,
+                                       init_val=float(self.app.defaults['tools_transform_skew_y']),
+                                       parent=self.app.ui)
+        val_box.set_icon(QtGui.QIcon(self.app.resource_location + '/skewY.png'))
 
         val, ok = val_box.get_value()
         if ok:
             self.on_skewx(val=val, ref=3)
-            self.app.inform.emit('[success] %s...' % _("Geometry shape skew on Y axis done"))
+            self.app.inform.emit('[success] %s...' % _("Skew on Y axis done"))
             return
         else:
-            self.app.inform.emit('[success] %s...' % _("Geometry shape skew on Y axis canceled"))
+            self.app.inform.emit('[success] %s...' % _("Skew on Y axis canceled"))
 
     @staticmethod
     def alt_bounds(shapelist):
@@ -1885,7 +1894,8 @@ class DrawTool(object):
     def utility_geometry(self, data=None):
         return None
 
-    def bounds(self, obj):
+    @staticmethod
+    def bounds(obj):
         def bounds_rec(o):
             if type(o) is list:
                 minx = np.Inf
@@ -1992,7 +2002,7 @@ class FCCircle(FCShapeTool):
 
         self.draw_app.app.jump_signal.disconnect()
 
-        self.draw_app.app.inform.emit('[success] %s' % _("Done. Adding Circle completed."))
+        self.draw_app.app.inform.emit('[success] %s' % _("Done."))
 
     def clean_up(self):
         self.draw_app.selected = []
@@ -2071,7 +2081,7 @@ class FCArc(FCShapeTool):
     def on_key(self, key):
         if key == 'D' or key == QtCore.Qt.Key_D:
             self.direction = 'cw' if self.direction == 'ccw' else 'ccw'
-            return _('Direction: %s') % self.direction.upper()
+            return '%s: %s' % (_('Direction'), self.direction.upper())
 
         # Jump to coords
         if key == QtCore.Qt.Key_J or key == 'J':
@@ -2232,7 +2242,7 @@ class FCArc(FCShapeTool):
 
         self.draw_app.app.jump_signal.disconnect()
 
-        self.draw_app.app.inform.emit('[success] %s' % _("Done. Arc completed."))
+        self.draw_app.app.inform.emit('[success] %s' % _("Done."))
 
     def clean_up(self):
         self.draw_app.selected = []
@@ -2305,7 +2315,7 @@ class FCRectangle(FCShapeTool):
         self.complete = True
 
         self.draw_app.app.jump_signal.disconnect()
-        self.draw_app.app.inform.emit('[success] %s' % _("Done. Rectangle completed."))
+        self.draw_app.app.inform.emit('[success] %s' % _("Done."))
 
     def clean_up(self):
         self.draw_app.selected = []
@@ -2380,7 +2390,7 @@ class FCPolygon(FCShapeTool):
 
         self.draw_app.app.jump_signal.disconnect()
 
-        self.draw_app.app.inform.emit('[success] %s' % _("Done. Polygon completed."))
+        self.draw_app.app.inform.emit('[success] %s' % _("Done."))
 
     def on_key(self, key):
         # Jump to coords
@@ -2437,7 +2447,7 @@ class FCPath(FCPolygon):
 
         self.draw_app.app.jump_signal.disconnect()
 
-        self.draw_app.app.inform.emit('[success] %s' % _("Done. Path completed."))
+        self.draw_app.app.inform.emit('[success] %s' % _("Done."))
 
     def utility_geometry(self, data=None):
         if len(self.points) > 0:
@@ -2596,7 +2606,7 @@ class FCExplode(FCShapeTool):
 
         self.draw_app.active_tool = self
         if len(self.draw_app.get_selected()) == 0:
-            self.draw_app.app.inform.emit('[WARNING_NOTCL] %s...' % _("No shape selected. Select a shape to explode"))
+            self.draw_app.app.inform.emit('[WARNING_NOTCL] %s' % _("No shape selected."))
         else:
             self.make()
 
@@ -2629,7 +2639,7 @@ class FCExplode(FCShapeTool):
             geo_list.append(DrawToolShape(line))
         self.geometry = geo_list
         self.draw_app.on_shape_complete()
-        self.draw_app.app.inform.emit('[success] %s...' % _("Done. Polygons exploded into lines."))
+        self.draw_app.app.inform.emit('[success] %s...' % _("Done."))
 
     def clean_up(self):
         self.draw_app.selected = []
@@ -2660,16 +2670,15 @@ class FCMove(FCShapeTool):
         self.selection_shape = self.selection_bbox()
 
         if len(self.draw_app.get_selected()) == 0:
-            self.draw_app.app.inform.emit('[WARNING_NOTCL] %s...' %
-                                          _("MOVE: No shape selected. Select a shape to move"))
+            self.draw_app.app.inform.emit('[WARNING_NOTCL] %s' % _("No shape selected."))
             return
         else:
-            self.draw_app.app.inform.emit(_(" MOVE: Click on reference point ..."))
+            self.draw_app.app.inform.emit(_("Click on reference location ..."))
 
         self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x))
 
     def set_origin(self, origin):
-        self.draw_app.app.inform.emit(_(" Click on destination point ..."))
+        self.draw_app.app.inform.emit(_("Click on destination point ..."))
         self.origin = origin
 
     def click(self, point):
@@ -2684,7 +2693,7 @@ class FCMove(FCShapeTool):
             # self.draw_app.app.inform.emit(_("[WARNING_NOTCL] Move cancelled. No shape selected."))
             self.select_shapes(point)
             self.draw_app.replot()
-            self.draw_app.app.inform.emit(_(" MOVE: Click on reference point ..."))
+            self.draw_app.app.inform.emit(_("Click on reference location ..."))
             return
 
         if self.origin is None:
@@ -2699,7 +2708,7 @@ class FCMove(FCShapeTool):
             return "Done."
 
     def make(self):
-        with self.draw_app.app.proc_container.new("Moving Geometry ..."):
+        with self.draw_app.app.proc_container.new(_("Moving ...")):
             # Create new geometry
             dx = self.destination[0] - self.origin[0]
             dy = self.destination[1] - self.origin[1]
@@ -2709,7 +2718,7 @@ class FCMove(FCShapeTool):
             # Delete old
             self.draw_app.delete_selected()
             self.complete = True
-            self.draw_app.app.inform.emit('[success] %s' % _("Done. Geometry(s) Move completed."))
+            self.draw_app.app.inform.emit('[success] %s' % _("Done."))
             try:
                 self.draw_app.app.jump_signal.disconnect()
             except TypeError:
@@ -2842,7 +2851,7 @@ class FCCopy(FCMove):
         self.geometry = [DrawToolShape(affinity.translate(geom.geo, xoff=dx, yoff=dy))
                          for geom in self.draw_app.get_selected()]
         self.complete = True
-        self.draw_app.app.inform.emit('[success] %s' % _("Done. Geometry(s) Copy completed."))
+        self.draw_app.app.inform.emit('[success] %s' % _("Done."))
         try:
             self.draw_app.app.jump_signal.disconnect()
         except (TypeError, AttributeError):
@@ -2915,7 +2924,7 @@ class FCText(FCShapeTool):
         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."))
 
     def utility_geometry(self, data=None):
         """
@@ -2963,7 +2972,7 @@ class FCBuffer(FCShapeTool):
 
     def on_buffer(self):
         if not self.draw_app.selected:
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. No shape selected."))
+            self.draw_app.app.inform.emit('[WARNING_NOTCL] %s %s' % (_("Cancelled."), _("No shape selected.")))
             return
 
         try:
@@ -2987,11 +2996,11 @@ class FCBuffer(FCShapeTool):
         self.disactivate()
         if ret_val == 'fail':
             return
-        self.draw_app.app.inform.emit('[success] %s' % _("Done. Buffer Tool completed."))
+        self.draw_app.app.inform.emit('[success] %s' % _("Done."))
 
     def on_buffer_int(self):
         if not self.draw_app.selected:
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. No shape selected."))
+            self.draw_app.app.inform.emit('[WARNING_NOTCL] %s %s' % (_("Cancelled."), _("No shape selected.")))
             return
 
         try:
@@ -3015,11 +3024,11 @@ class FCBuffer(FCShapeTool):
         self.disactivate()
         if ret_val == 'fail':
             return
-        self.draw_app.app.inform.emit('[success] %s' % _("Done. Buffer Int Tool completed."))
+        self.draw_app.app.inform.emit('[success] %s' % _("Done."))
 
     def on_buffer_ext(self):
         if not self.draw_app.selected:
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. No shape selected."))
+            self.draw_app.app.inform.emit('[WARNING_NOTCL] %s %s' % (_("Cancelled."), _("No shape selected.")))
             return
 
         try:
@@ -3030,7 +3039,7 @@ class FCBuffer(FCShapeTool):
                 buffer_distance = float(self.buff_tool.buffer_distance_entry.get_value().replace(',', '.'))
                 self.buff_tool.buffer_distance_entry.set_value(buffer_distance)
             except ValueError:
-                self.app.inform.emit('[WARNING_NOTCL] %s' %
+                self.draw_app.app.inform.emit('[WARNING_NOTCL] %s' %
                                      _("Buffer distance value is missing or wrong format. Add it and retry."))
                 return
         # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment
@@ -3043,7 +3052,7 @@ class FCBuffer(FCShapeTool):
         self.disactivate()
         if ret_val == 'fail':
             return
-        self.draw_app.app.inform.emit('[success] %s' % _("Done. Buffer Ext Tool completed."))
+        self.draw_app.app.inform.emit('[success] %s' % _("Done."))
 
     def activate(self):
         self.buff_tool.buffer_button.clicked.disconnect()
@@ -3159,7 +3168,7 @@ class FCEraser(FCShapeTool):
 
         self.draw_app.delete_utility_geometry()
         self.draw_app.plot_all()
-        self.draw_app.app.inform.emit('[success] %s' % _("Done. Eraser tool action completed."))
+        self.draw_app.app.inform.emit('[success] %s' % _("Done."))
         try:
             self.draw_app.app.jump_signal.disconnect()
         except (TypeError, AttributeError):
@@ -3314,22 +3323,7 @@ class AppGeoEditor(QtCore.QObject):
         self.exit_editor_button.clicked.connect(lambda: self.app.editor2object())
 
         # ## Toolbar events and properties
-        self.tools = {
-            "select": {"button": self.app.ui.geo_select_btn, "constructor": FCSelect},
-            "arc": {"button": self.app.ui.geo_add_arc_btn, "constructor": FCArc},
-            "circle": {"button": self.app.ui.geo_add_circle_btn, "constructor": FCCircle},
-            "path": {"button": self.app.ui.geo_add_path_btn, "constructor": FCPath},
-            "rectangle": {"button": self.app.ui.geo_add_rectangle_btn, "constructor": FCRectangle},
-            "polygon": {"button": self.app.ui.geo_add_polygon_btn, "constructor": FCPolygon},
-            "text": {"button": self.app.ui.geo_add_text_btn, "constructor": FCText},
-            "buffer": {"button": self.app.ui.geo_add_buffer_btn, "constructor": FCBuffer},
-            "paint": {"button": self.app.ui.geo_add_paint_btn, "constructor": FCPaint},
-            "eraser": {"button": self.app.ui.geo_eraser_btn, "constructor": FCEraser},
-            "move": {"button": self.app.ui.geo_move_btn, "constructor": FCMove},
-            "transform": {"button": self.app.ui.geo_transform_btn, "constructor": FCTransform},
-            "copy": {"button": self.app.ui.geo_copy_btn, "constructor": FCCopy},
-            "explode": {"button": self.app.ui.geo_explode_btn, "constructor": FCExplode}
-        }
+        self.tools = {}
 
         # # ## Data
         self.active_tool = None
@@ -3385,15 +3379,6 @@ class AppGeoEditor(QtCore.QObject):
         # this will flag if the Editor "tools" are launched from key shortcuts (True) or from menu toolbar (False)
         self.launched_from_shortcuts = False
 
-        def make_callback(thetool):
-            def f():
-                self.on_tool_select(thetool)
-            return f
-
-        for tool in self.tools:
-            self.tools[tool]["button"].triggered.connect(make_callback(tool))  # Events
-            self.tools[tool]["button"].setCheckable(True)  # Checkable
-
         self.app.ui.grid_snap_btn.triggered.connect(self.on_grid_toggled)
         self.app.ui.corner_snap_btn.setCheckable(True)
         self.app.ui.corner_snap_btn.triggered.connect(lambda: self.toolbar_tool_toggle("corner_snap"))
@@ -3435,6 +3420,14 @@ class AppGeoEditor(QtCore.QObject):
         self.paint_tool = PaintOptionsTool(self.app, self)
         self.transform_tool = TransformEditorTool(self.app, self)
 
+        # #############################################################################################################
+        # ####################### GEOMETRY Editor Signals #############################################################
+        # #############################################################################################################
+
+        # connect the toolbar signals
+        self.connect_geo_toolbar_signals()
+
+        # connect Geometry Editor Menu signals
         self.app.ui.geo_add_circle_menuitem.triggered.connect(lambda: self.select_tool('circle'))
         self.app.ui.geo_add_arc_menuitem.triggered.connect(lambda: self.select_tool('arc'))
         self.app.ui.geo_add_rectangle_menuitem.triggered.connect(lambda: self.select_tool('rectangle'))
@@ -3468,10 +3461,36 @@ class AppGeoEditor(QtCore.QObject):
         self.mm = None
         self.mr = None
 
-        # store the status of the editor so the Delete at object level will not work until the edit is finished
-        self.editor_active = False
         log.debug("Initialization of the Geometry Editor is finished ...")
 
+    def make_callback(self, thetool):
+        def f():
+            self.on_tool_select(thetool)
+
+        return f
+
+    def connect_geo_toolbar_signals(self):
+        self.tools.update({
+            "select": {"button": self.app.ui.geo_select_btn, "constructor": FCSelect},
+            "arc": {"button": self.app.ui.geo_add_arc_btn, "constructor": FCArc},
+            "circle": {"button": self.app.ui.geo_add_circle_btn, "constructor": FCCircle},
+            "path": {"button": self.app.ui.geo_add_path_btn, "constructor": FCPath},
+            "rectangle": {"button": self.app.ui.geo_add_rectangle_btn, "constructor": FCRectangle},
+            "polygon": {"button": self.app.ui.geo_add_polygon_btn, "constructor": FCPolygon},
+            "text": {"button": self.app.ui.geo_add_text_btn, "constructor": FCText},
+            "buffer": {"button": self.app.ui.geo_add_buffer_btn, "constructor": FCBuffer},
+            "paint": {"button": self.app.ui.geo_add_paint_btn, "constructor": FCPaint},
+            "eraser": {"button": self.app.ui.geo_eraser_btn, "constructor": FCEraser},
+            "move": {"button": self.app.ui.geo_move_btn, "constructor": FCMove},
+            "transform": {"button": self.app.ui.geo_transform_btn, "constructor": FCTransform},
+            "copy": {"button": self.app.ui.geo_copy_btn, "constructor": FCCopy},
+            "explode": {"button": self.app.ui.geo_explode_btn, "constructor": FCExplode}
+        })
+
+        for tool in self.tools:
+            self.tools[tool]["button"].triggered.connect(self.make_callback(tool))  # Events
+            self.tools[tool]["button"].setCheckable(True)  # Checkable
+
     def pool_recreated(self, pool):
         self.shapes.pool = pool
         self.tool_shape.pool = pool
@@ -3646,9 +3665,6 @@ class AppGeoEditor(QtCore.QObject):
         # for w in sel_tab_widget_list:
         #     w.setEnabled(False)
 
-        # Tell the App that the editor is active
-        self.editor_active = True
-
         self.item_selected.connect(self.on_geo_elem_selected)
 
         # ## appGUI Events
@@ -3678,7 +3694,6 @@ class AppGeoEditor(QtCore.QObject):
         self.clear()
         self.app.ui.geo_edit_toolbar.setDisabled(True)
 
-        settings = QSettings("Open Source", "FlatCAM")
         self.app.ui.corner_snap_btn.setVisible(False)
         self.app.ui.snap_magnet.setVisible(False)
 
@@ -3698,9 +3713,6 @@ class AppGeoEditor(QtCore.QObject):
         self.app.ui.g_editor_cmenu.setEnabled(False)
         self.app.ui.e_editor_cmenu.setEnabled(False)
 
-        # Tell the app that the editor is no longer active
-        self.editor_active = False
-
         self.app.ui.popmenu_disable.setVisible(True)
         self.app.ui.cmenu_newmenu.menuAction().setVisible(True)
         self.app.ui.popmenu_properties.setVisible(True)
@@ -4397,7 +4409,7 @@ class AppGeoEditor(QtCore.QObject):
 
     def on_copy_click(self):
         if not self.selected:
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. No shape selected."))
+            self.app.inform.emit('[WARNING_NOTCL] %s %s' % (_("Cancelled."), _("No shape selected.")))
             return
 
         self.app.ui.geo_copy_btn.setChecked(True)
@@ -4791,7 +4803,7 @@ class AppGeoEditor(QtCore.QObject):
         except Exception as e:
             log.debug("AppGeoEditor.intersection() --> %s" % str(e))
             self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                 _("A selection of at least 2 geo items is required to do Intersection."))
+                                 _("A selection of minimum two items is required to do Intersection."))
             self.select_tool('select')
             return
 
@@ -4826,7 +4838,7 @@ class AppGeoEditor(QtCore.QObject):
         except Exception as e:
             log.debug("AppGeoEditor.intersection() --> %s" % str(e))
             self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                 _("A selection of at least 2 geo items is required to do Intersection."))
+                                 _("A selection of minimum two items is required to do Intersection."))
             self.select_tool('select')
             return
 
@@ -4921,13 +4933,11 @@ class AppGeoEditor(QtCore.QObject):
             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."))
             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."))
 
             # deselect everything
             self.selected = []
@@ -4975,11 +4985,11 @@ class AppGeoEditor(QtCore.QObject):
             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."))
             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."))
             # deselect everything
             self.selected = []
             self.replot()
@@ -4999,7 +5009,7 @@ class AppGeoEditor(QtCore.QObject):
 
         if not results:
             self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                 _("Failed, the result is empty. Choose a smaller buffer value."))
+                                 _("Failed, the result is empty. Choose a different buffer value."))
             # deselect everything
             self.selected = []
             self.replot()
@@ -5024,13 +5034,11 @@ class AppGeoEditor(QtCore.QObject):
             return
 
         if len(selected) == 0:
-            self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                 _("Nothing selected for buffering."))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Nothing selected."))
             return
 
         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."))
             # deselect everything
             self.selected = []
             self.replot()
@@ -5073,7 +5081,7 @@ class AppGeoEditor(QtCore.QObject):
         selected = self.get_selected()
 
         if len(selected) == 0:
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Nothing selected for painting."))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Nothing selected."))
             return
 
         for param in [tooldia, overlap, margin]:
@@ -5149,7 +5157,7 @@ class AppGeoEditor(QtCore.QObject):
         # This is a dirty patch:
         for r in results:
             self.add_shape(DrawToolShape(r))
-        self.app.inform.emit('[success] %s' % _("Paint done."))
+        self.app.inform.emit('[success] %s' % _("Done."))
         self.replot()
 
     def flatten(self, geometry, orient_val=1, reset=True, pathonly=False):

File diff suppressed because it is too large
+ 413 - 212
appEditors/AppGerberEditor.py


+ 4 - 2
appEditors/AppTextEditor.py

@@ -251,7 +251,9 @@ class AppTextEditor(QtWidgets.QWidget):
                 ext_filter=_filter_
             )[0])
         except TypeError:
-            filename = str(FCFileSaveDialog.get_saved_filename(caption=_("Export Code ..."), ext_filter=_filter_)[0])
+            filename = str(FCFileSaveDialog.get_saved_filename(
+                caption=_("Export Code ..."),
+                ext_filter=_filter_)[0])
 
         if filename == "":
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
@@ -330,7 +332,7 @@ class AppTextEditor(QtWidgets.QWidget):
         r = self.code_editor.find(str(text_to_be_found), flags)
         if r is False:
             self.code_editor.moveCursor(QtGui.QTextCursor.Start)
-            r = self.code_editor.find(str(text_to_be_found), flags)
+            self.code_editor.find(str(text_to_be_found), flags)
 
     def handleReplaceGCode(self):
 

+ 7 - 12
appEditors/appGCodeEditor.py

@@ -43,8 +43,13 @@ class AppGCodeEditor(QtCore.QObject):
         self.gcode_obj = None
         self.code_edited = ''
 
-        # store the status of the editor so the Delete at object level will not work until the edit is finished
-        self.editor_active = False
+        # #################################################################################
+        # ################### SIGNALS #####################################################
+        # #################################################################################
+        self.ui.name_entry.returnPressed.connect(self.on_name_activate)
+        self.ui.update_gcode_button.clicked.connect(self.insert_gcode)
+        self.ui.exit_editor_button.clicked.connect(lambda: self.app.editor2object())
+
         log.debug("Initialization of the GCode Editor is finished ...")
 
     def set_ui(self):
@@ -103,13 +108,6 @@ class AppGCodeEditor(QtCore.QObject):
 
         self.activate()
 
-        # #################################################################################
-        # ################### SIGNALS #####################################################
-        # #################################################################################
-        self.ui.name_entry.returnPressed.connect(self.on_name_activate)
-        self.ui.update_gcode_button.clicked.connect(self.insert_gcode)
-        self.ui.exit_editor_button.clicked.connect(lambda: self.app.editor2object())
-
     def build_ui(self):
         """
 
@@ -464,7 +462,6 @@ class AppGCodeEditor(QtCore.QObject):
                     end_sel = my_text_cursor.selectionEnd()
                 else:
                     pos_list = []
-                    end_sel = 0
 
                     my_text_cursor = self.edit_area.textCursor()
                     m6_pos = my_text_cursor.selectionEnd()
@@ -610,11 +607,9 @@ class AppGCodeEditor(QtCore.QObject):
                 file.close()
 
     def activate(self):
-        self.editor_active = True
         self.app.call_source = 'gcode_editor'
 
     def deactivate(self):
-        self.editor_active = False
         self.app.call_source = 'app'
 
     def on_name_activate(self):

File diff suppressed because it is too large
+ 777 - 22
appGUI/GUIElements.py


File diff suppressed because it is too large
+ 278 - 163
appGUI/MainGUI.py


File diff suppressed because it is too large
+ 162 - 139
appGUI/ObjectUI.py


+ 25 - 19
appGUI/PlotCanvas.py

@@ -191,13 +191,13 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
 
         # enable the HUD if it is activated in FlatCAM Preferences
         if self.fcapp.defaults['global_hud'] is True:
-            self.on_toggle_hud(state=True)
+            self.on_toggle_hud(state=True, silent=True)
 
         # Axis Display
         self.axis_enabled = True
 
         # enable Axis
-        self.on_toggle_axis(state=True)
+        self.on_toggle_axis(state=True, silent=True)
 
         # enable Grid lines
         self.grid_lines_enabled = True
@@ -218,8 +218,8 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
 
         self.graph_event_connect('mouse_wheel', self.on_mouse_scroll)
 
-    def on_toggle_axis(self, signal=None, state=None):
-        if state is None:
+    def on_toggle_axis(self, signal=None, state=None, silent=None):
+        if not state:
             state = not self.axis_enabled
 
         if state:
@@ -234,16 +234,18 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
                                                               background-color: orange;
                                                           }
                                                           """)
-            self.fcapp.inform[str, bool].emit(_("Axis enabled."), False)
+            if silent is None:
+                self.fcapp.inform[str, bool].emit(_("Axis enabled."), False)
         else:
             self.axis_enabled = False
             self.fcapp.defaults['global_axis'] = False
             self.v_line.parent = None
             self.h_line.parent = None
             self.fcapp.ui.axis_status_label.setStyleSheet("")
-            self.fcapp.inform[str, bool].emit(_("Axis disabled."), False)
+            if silent is None:
+                self.fcapp.inform[str, bool].emit(_("Axis disabled."), False)
 
-    def on_toggle_hud(self, signal=None, state=None):
+    def on_toggle_hud(self, signal=None, state=None, silent=None):
         if state is None:
             state = not self.hud_enabled
 
@@ -259,7 +261,8 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
                                                       background-color: mediumpurple;
                                                   }
                                                   """)
-            self.fcapp.inform[str, bool].emit(_("HUD enabled."), False)
+            if silent is None:
+                self.fcapp.inform[str, bool].emit(_("HUD enabled."), False)
 
         else:
             self.hud_enabled = False
@@ -267,21 +270,24 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
             self.text_hud.parent = None
             self.fcapp.defaults['global_hud'] = False
             self.fcapp.ui.hud_label.setStyleSheet("")
-            self.fcapp.inform[str, bool].emit(_("HUD disabled."), False)
+            if silent is None:
+                self.fcapp.inform[str, bool].emit(_("HUD disabled."), False)
 
-    def on_toggle_grid_lines(self):
+    def on_toggle_grid_lines(self, signal=None, silent=None):
         state = not self.grid_lines_enabled
 
         if state:
             self.fcapp.defaults['global_grid_lines'] = True
             self.grid_lines_enabled = True
             self.grid.parent = self.view.scene
-            self.fcapp.inform[str, bool].emit(_("Grid enabled."), False)
+            if silent is None:
+                self.fcapp.inform[str, bool].emit(_("Grid enabled."), False)
         else:
             self.fcapp.defaults['global_grid_lines'] = False
             self.grid_lines_enabled = False
             self.grid.parent = None
-            self.fcapp.inform[str, bool].emit(_("Grid disabled."), False)
+            if silent is None:
+                self.fcapp.inform[str, bool].emit(_("Grid disabled."), False)
 
         # HACK: enabling/disabling the cursor seams to somehow update the shapes on screen
         # - perhaps is a bug in VisPy implementation
@@ -569,13 +575,13 @@ class CursorBig(QtCore.QObject):
 
     def set_data(self, pos, **kwargs):
         """Internal event handler to draw the cursor when the mouse moves."""
-        if 'edge_color' in kwargs:
-            color = kwargs['edge_color']
-        else:
-            if self.app.defaults['global_theme'] == 'white':
-                color = '#000000FF'
-            else:
-                color = '#FFFFFFFF'
+        # if 'edge_color' in kwargs:
+        #     color = kwargs['edge_color']
+        # else:
+        #     if self.app.defaults['global_theme'] == 'white':
+        #         color = '#000000FF'
+        #     else:
+        #         color = '#FFFFFFFF'
 
         position = [pos[0][0], pos[0][1]]
         self.mouse_position_updated.emit(position)

+ 29 - 15
appGUI/PlotCanvasLegacy.py

@@ -306,9 +306,13 @@ class PlotCanvasLegacy(QtCore.QObject):
         # signal if there is a doubleclick
         self.is_dblclk = False
 
+        # HUD Display
         self.hud_enabled = False
         self.text_hud = self.Thud(plotcanvas=self)
 
+        if self.app.defaults['global_hud'] is True:
+            self.on_toggle_hud(state=True, silent=None)
+
         # enable Grid lines
         self.grid_lines_enabled = True
 
@@ -317,17 +321,21 @@ class PlotCanvasLegacy(QtCore.QObject):
         if self.app.defaults['global_workspace'] is True:
             self.draw_workspace(workspace_size=self.app.defaults["global_workspaceT"])
 
-        if self.app.defaults['global_hud'] is True:
-            self.on_toggle_hud(state=True)
-
         # Axis Display
         self.axis_enabled = True
 
         # enable Axis
-        self.on_toggle_axis(state=True)
-
-    def on_toggle_axis(self, signal=None, state=None):
-        if state is None:
+        self.on_toggle_axis(state=True, silent=True)
+        self.app.ui.axis_status_label.setStyleSheet("""
+                                                    QLabel
+                                                    {
+                                                        color: black;
+                                                        background-color: orange;
+                                                    }
+                                                    """)
+
+    def on_toggle_axis(self, signal=None, state=None, silent=None):
+        if not state:
             state = not self.axis_enabled
 
         if state:
@@ -343,7 +351,8 @@ class PlotCanvasLegacy(QtCore.QObject):
                                                                 background-color: orange;
                                                             }
                                                             """)
-                self.app.inform[str, bool].emit(_("Axis enabled."), False)
+                if silent is None:
+                    self.app.inform[str, bool].emit(_("Axis enabled."), False)
         else:
             self.axis_enabled = False
             self.app.defaults['global_axis'] = False
@@ -351,11 +360,12 @@ class PlotCanvasLegacy(QtCore.QObject):
                 self.axes.lines.remove(self.h_line)
                 self.axes.lines.remove(self.v_line)
                 self.app.ui.axis_status_label.setStyleSheet("")
-                self.app.inform[str, bool].emit(_("Axis disabled."), False)
+                if silent is None:
+                    self.app.inform[str, bool].emit(_("Axis disabled."), False)
 
         self.canvas.draw()
 
-    def on_toggle_hud(self, signal=None, state=None):
+    def on_toggle_hud(self, signal=None, state=None, silent=None):
         if state is None:
             state = not self.hud_enabled
 
@@ -371,13 +381,15 @@ class PlotCanvasLegacy(QtCore.QObject):
                                                     background-color: mediumpurple;
                                                 }
                                                 """)
-            self.app.inform[str, bool].emit(_("HUD enabled."), False)
+            if silent is None:
+                self.app.inform[str, bool].emit(_("HUD enabled."), False)
         else:
             self.hud_enabled = False
             self.text_hud.remove_artist()
             self.app.defaults['global_hud'] = False
             self.app.ui.hud_label.setStyleSheet("")
-            self.app.inform[str, bool].emit(_("HUD disabled."), False)
+            if silent is None:
+                self.app.inform[str, bool].emit(_("HUD disabled."), False)
 
         self.canvas.draw()
 
@@ -440,7 +452,7 @@ class PlotCanvasLegacy(QtCore.QObject):
             if self.hud_holder in self.p.axes.artists:
                 self.p.axes.artists.remove(self.hud_holder)
 
-    def on_toggle_grid_lines(self):
+    def on_toggle_grid_lines(self, signal=None, silent=None):
         state = not self.grid_lines_enabled
 
         if state:
@@ -451,7 +463,8 @@ class PlotCanvasLegacy(QtCore.QObject):
                 self.canvas.draw()
             except IndexError:
                 pass
-            self.app.inform[str, bool].emit(_("Grid enabled."), False)
+            if silent is None:
+                self.app.inform[str, bool].emit(_("Grid enabled."), False)
         else:
             self.app.defaults['global_grid_lines'] = False
             self.grid_lines_enabled = False
@@ -460,7 +473,8 @@ class PlotCanvasLegacy(QtCore.QObject):
                 self.canvas.draw()
             except IndexError:
                 pass
-            self.app.inform[str, bool].emit(_("Grid disabled."), False)
+            if silent is None:
+                self.app.inform[str, bool].emit(_("Grid disabled."), False)
 
     def draw_workspace(self, workspace_size):
         """

+ 3 - 2
appGUI/VisPyTesselators.py

@@ -29,9 +29,10 @@ class GLUTess:
         pass
 
     def _on_combine(self, coords, data, weight):
-        return (coords[0], coords[1], coords[2])
+        return coords[0], coords[1], coords[2]
 
-    def _on_error(self, errno):
+    @staticmethod
+    def _on_error(errno):
         print("GLUTess error:", errno)
 
     def _on_end_primitive(self):

+ 3 - 2
appGUI/VisPyVisuals.py

@@ -339,7 +339,7 @@ class ShapeCollectionVisual(CompoundVisual):
         if update:
             self.__update()
 
-    def update_visibility(self, state:bool, indexes=None) -> None:
+    def update_visibility(self, state: bool, indexes=None) -> None:
         # Lock sub-visuals updates
         self.update_lock.acquire(True)
         if indexes is None:
@@ -540,8 +540,9 @@ class ShapeCollectionVisual(CompoundVisual):
     def redraw(self, indexes=None, update_colors=None):
         """
         Redraws collection
-        :param indexes: list
+        :param indexes:     list
             Shape indexes to get from process pool
+        :param update_colors:
         """
         # Only one thread can update data
         self.results_lock.acquire(True)

+ 22 - 4
appGUI/preferences/PreferencesUIManager.py

@@ -42,6 +42,8 @@ class PreferencesUIManager:
         # if Preferences are changed in the Edit -> Preferences tab the value will be set to True
         self.preferences_changed_flag = False
 
+        self.old_color = QtGui.QColor('black')
+
         # when adding entries here read the comments in the  method found below named:
         # def app_obj.new_object(self, kind, name, initialize, active=True, fit=True, plot=True)
         self.defaults_form_fields = {
@@ -282,6 +284,14 @@ class PreferencesUIManager:
             "geometry_area_shape":      self.ui.geometry_defaults_form.geometry_adv_opt_group.area_shape_radio,
             "geometry_area_strategy":   self.ui.geometry_defaults_form.geometry_adv_opt_group.strategy_radio,
             "geometry_area_overz":      self.ui.geometry_defaults_form.geometry_adv_opt_group.over_z_entry,
+            # Polish
+            "geometry_polish":          self.ui.geometry_defaults_form.geometry_adv_opt_group.polish_cb,
+            "geometry_polish_dia":      self.ui.geometry_defaults_form.geometry_adv_opt_group.polish_dia_entry,
+            "geometry_polish_pressure": self.ui.geometry_defaults_form.geometry_adv_opt_group.polish_pressure_entry,
+            "geometry_polish_travelz":  self.ui.geometry_defaults_form.geometry_adv_opt_group.polish_travelz_entry,
+            "geometry_polish_margin":   self.ui.geometry_defaults_form.geometry_adv_opt_group.polish_margin_entry,
+            "geometry_polish_overlap":  self.ui.geometry_defaults_form.geometry_adv_opt_group.polish_over_entry,
+            "geometry_polish_method":   self.ui.geometry_defaults_form.geometry_adv_opt_group.polish_method_combo,
 
             # Geometry Editor
             "geometry_editor_sel_limit":        self.ui.geometry_defaults_form.geometry_editor_group.sel_limit_entry,
@@ -488,6 +498,7 @@ class PreferencesUIManager:
             "tools_calc_vshape_cut_z": self.ui.tools_defaults_form.tools_calculators_group.cut_z_entry,
             "tools_calc_electro_length": self.ui.tools_defaults_form.tools_calculators_group.pcblength_entry,
             "tools_calc_electro_width": self.ui.tools_defaults_form.tools_calculators_group.pcbwidth_entry,
+            "tools_calc_electro_area": self.ui.tools_defaults_form.tools_calculators_group.area_entry,
             "tools_calc_electro_cdensity": self.ui.tools_defaults_form.tools_calculators_group.cdensity_entry,
             "tools_calc_electro_growth": self.ui.tools_defaults_form.tools_calculators_group.growth_entry,
 
@@ -530,13 +541,17 @@ class PreferencesUIManager:
             "tools_solderpaste_speedrev": self.ui.tools_defaults_form.tools_solderpaste_group.speedrev_entry,
             "tools_solderpaste_dwellrev": self.ui.tools_defaults_form.tools_solderpaste_group.dwellrev_entry,
             "tools_solderpaste_pp": self.ui.tools_defaults_form.tools_solderpaste_group.pp_combo,
+
+            # Subtractor Tool
             "tools_sub_close_paths": self.ui.tools_defaults_form.tools_sub_group.close_paths_cb,
+            "tools_sub_delete_sources":  self.ui.tools_defaults_form.tools_sub_group.delete_sources_cb,
 
             # Corner Markers Tool
-
+            "tools_corners_type": self.ui.tools_defaults_form.tools_corners_group.type_radio,
             "tools_corners_thickness": self.ui.tools_defaults_form.tools_corners_group.thick_entry,
             "tools_corners_length": self.ui.tools_defaults_form.tools_corners_group.l_entry,
             "tools_corners_margin": self.ui.tools_defaults_form.tools_corners_group.margin_entry,
+            "tools_corners_drill_dia": self.ui.tools_defaults_form.tools_corners_group.drill_dia_entry,
 
             # #######################################################################################################
             # ########################################## TOOLS 2 ####################################################
@@ -582,6 +597,7 @@ class PreferencesUIManager:
             # Copper Thieving Tool
             "tools_copper_thieving_clearance": self.ui.tools2_defaults_form.tools2_cfill_group.clearance_entry,
             "tools_copper_thieving_margin": self.ui.tools2_defaults_form.tools2_cfill_group.margin_entry,
+            "tools_copper_thieving_area": self.ui.tools2_defaults_form.tools2_cfill_group.area_entry,
             "tools_copper_thieving_reference": self.ui.tools2_defaults_form.tools2_cfill_group.reference_radio,
             "tools_copper_thieving_box_type": self.ui.tools2_defaults_form.tools2_cfill_group.bbox_type_radio,
             "tools_copper_thieving_circle_steps": self.ui.tools2_defaults_form.tools2_cfill_group.circlesteps_entry,
@@ -596,6 +612,7 @@ class PreferencesUIManager:
             "tools_copper_thieving_rb_margin": self.ui.tools2_defaults_form.tools2_cfill_group.rb_margin_entry,
             "tools_copper_thieving_rb_thickness": self.ui.tools2_defaults_form.tools2_cfill_group.rb_thickness_entry,
             "tools_copper_thieving_mask_clearance": self.ui.tools2_defaults_form.tools2_cfill_group.clearance_ppm_entry,
+            "tools_copper_thieving_geo_choice": self.ui.tools2_defaults_form.tools2_cfill_group.ppm_choice_radio,
 
             # Fiducials Tool
             "tools_fiducials_dia": self.ui.tools2_defaults_form.tools2_fiducials_group.dia_entry,
@@ -897,7 +914,7 @@ class PreferencesUIManager:
         # Preferences save, update the color of the Preferences Tab text
         for idx in range(self.ui.plot_tab_area.count()):
             if self.ui.plot_tab_area.tabText(idx) == _("Preferences"):
-                self.ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('black'))
+                self.ui.plot_tab_area.tabBar.setTabTextColor(idx, self.old_color)
 
         # restore the default stylesheet by setting a blank one
         self.ui.pref_apply_button.setStyleSheet("")
@@ -990,7 +1007,7 @@ class PreferencesUIManager:
             # close the tab and delete it
             for idx in range(self.ui.plot_tab_area.count()):
                 if self.ui.plot_tab_area.tabText(idx) == _("Preferences"):
-                    self.ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('black'))
+                    self.ui.plot_tab_area.tabBar.setTabTextColor(idx, self.old_color)
                     self.ui.plot_tab_area.closeTab(idx)
                     break
 
@@ -1018,7 +1035,7 @@ class PreferencesUIManager:
         # Preferences save, update the color of the Preferences Tab text
         for idx in range(self.ui.plot_tab_area.count()):
             if self.ui.plot_tab_area.tabText(idx) == _("Preferences"):
-                self.ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('black'))
+                self.ui.plot_tab_area.tabBar.setTabTextColor(idx, self.old_color)
                 self.ui.plot_tab_area.closeTab(idx)
                 break
 
@@ -1123,6 +1140,7 @@ class PreferencesUIManager:
 
             for idx in range(self.ui.plot_tab_area.count()):
                 if self.ui.plot_tab_area.tabText(idx) == _("Preferences"):
+                    self.old_color = self.ui.plot_tab_area.tabBar.tabTextColor(idx)
                     self.ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('red'))
 
             self.ui.pref_apply_button.setStyleSheet("QPushButton {color: red;}")

+ 1 - 0
appGUI/preferences/__init__.py

@@ -1,4 +1,5 @@
 from appGUI.GUIElements import *
+from PyQt5.QtCore import QSettings
 import gettext
 import appTranslation as fcTranslate
 import builtins

+ 6 - 6
appGUI/preferences/cncjob/CNCJobAdvOptPrefGroupUI.py

@@ -122,7 +122,7 @@ class CNCJobAdvOptPrefGroupUI(OptionsGroupUI):
         )
         self.ptravelz_entry = FCDoubleSpinner()
         self.ptravelz_entry.set_precision(self.decimals)
-        self.ptravelz_entry.set_range(0.0000, 9999.9999)
+        self.ptravelz_entry.set_range(0.0000, 10000.0000)
 
         grid0.addWidget(self.ptravelz_label, 14, 0)
         grid0.addWidget(self.ptravelz_entry, 14, 1)
@@ -135,7 +135,7 @@ class CNCJobAdvOptPrefGroupUI(OptionsGroupUI):
         )
         self.pdepth_entry = FCDoubleSpinner()
         self.pdepth_entry.set_precision(self.decimals)
-        self.pdepth_entry.set_range(-99999.9999, 0.0000)
+        self.pdepth_entry.set_range(-910000.0000, 0.0000)
 
         grid0.addWidget(self.pdepth_label, 16, 0)
         grid0.addWidget(self.pdepth_entry, 16, 1)
@@ -147,7 +147,7 @@ class CNCJobAdvOptPrefGroupUI(OptionsGroupUI):
         )
         self.feedrate_probe_entry = FCDoubleSpinner()
         self.feedrate_probe_entry.set_precision(self.decimals)
-        self.feedrate_probe_entry.set_range(0, 99999.9999)
+        self.feedrate_probe_entry.set_range(0, 910000.0000)
 
         grid0.addWidget(self.feedrate_probe_label, 18, 0)
         grid0.addWidget(self.feedrate_probe_entry, 18, 1)
@@ -176,7 +176,7 @@ class CNCJobAdvOptPrefGroupUI(OptionsGroupUI):
 
         self.jog_step_entry = FCDoubleSpinner()
         self.jog_step_entry.set_precision(self.decimals)
-        self.jog_step_entry.set_range(0, 99999.9999)
+        self.jog_step_entry.set_range(0, 910000.0000)
 
         grid0.addWidget(self.jog_step_label, 24, 0)
         grid0.addWidget(self.jog_step_entry, 24, 1)
@@ -189,7 +189,7 @@ class CNCJobAdvOptPrefGroupUI(OptionsGroupUI):
 
         self.jog_fr_entry = FCDoubleSpinner()
         self.jog_fr_entry.set_precision(self.decimals)
-        self.jog_fr_entry.set_range(0, 99999.9999)
+        self.jog_fr_entry.set_range(0, 910000.0000)
 
         grid0.addWidget(self.jog_fr_label, 26, 0)
         grid0.addWidget(self.jog_fr_entry, 26, 1)
@@ -202,7 +202,7 @@ class CNCJobAdvOptPrefGroupUI(OptionsGroupUI):
 
         self.jog_travelz_entry = FCDoubleSpinner()
         self.jog_travelz_entry.set_precision(self.decimals)
-        self.jog_travelz_entry.set_range(0, 99999.9999)
+        self.jog_travelz_entry.set_range(0, 910000.0000)
 
         grid0.addWidget(self.jog_travelz_label, 28, 0)
         grid0.addWidget(self.jog_travelz_entry, 28, 1)

+ 2 - 2
appGUI/preferences/cncjob/CNCJobGenPrefGroupUI.py

@@ -107,8 +107,8 @@ class CNCJobGenPrefGroupUI(OptionsGroupUI):
               "- Incremental G91 -> the reference is the previous position")
         )
         self.coords_type_radio = RadioSet([
-            {"label": _("Absolute G90"), "value": "G90"},
-            {"label": _("Incremental G91"), "value": "G91"}
+            {"label": _("Absolute"), "value": "G90"},
+            {"label": _("Incremental"), "value": "G91"}
         ], orientation='vertical', stretch=False)
         grid0.addWidget(coords_type_label, 8, 0)
         grid0.addWidget(self.coords_type_radio, 8, 1)

+ 1 - 1
appGUI/preferences/excellon/ExcellonAdvOptPrefGroupUI.py

@@ -33,7 +33,7 @@ class ExcellonAdvOptPrefGroupUI(OptionsGroupUI):
 
         self.exc_label = QtWidgets.QLabel('<b>%s:</b>' % _('Advanced Options'))
         self.exc_label.setToolTip(
-            _("A list of Excellon advanced parameters.\n"
+            _("A list of advanced parameters.\n"
               "Those parameters are available only for\n"
               "Advanced App. Level.")
         )

+ 4 - 4
appGUI/preferences/excellon/ExcellonEditorPrefGroupUI.py

@@ -103,7 +103,7 @@ class ExcellonEditorPrefGroupUI(OptionsGroupUI):
         )
         # self.drill_pitch_label.setMinimumWidth(100)
         self.drill_pitch_entry = FCDoubleSpinner()
-        self.drill_pitch_entry.set_range(0, 99999.9999)
+        self.drill_pitch_entry.set_range(0, 910000.0000)
         self.drill_pitch_entry.set_precision(self.decimals)
 
         grid0.addWidget(self.drill_pitch_label, 5, 0)
@@ -161,7 +161,7 @@ class ExcellonEditorPrefGroupUI(OptionsGroupUI):
         # Slot length
         self.slot_length_label = QtWidgets.QLabel('%s:' % _('Length'))
         self.slot_length_label.setToolTip(
-            _("Length = The length of the slot.")
+            _("Length. The length of the slot.")
         )
         self.slot_length_label.setMinimumWidth(100)
 
@@ -195,8 +195,8 @@ class ExcellonEditorPrefGroupUI(OptionsGroupUI):
         self.slot_angle_label.setToolTip(
             _("Angle at which the slot is placed.\n"
               "The precision is of max 2 decimals.\n"
-              "Min value is: -359.99 degrees.\n"
-              "Max value is:  360.00 degrees.")
+              "Min value is: -360.00 degrees.\n"
+              "Max value is: 360.00 degrees.")
         )
         self.slot_angle_label.setMinimumWidth(100)
 

+ 2 - 2
appGUI/preferences/excellon/ExcellonExpPrefGroupUI.py

@@ -43,8 +43,8 @@ class ExcellonExpPrefGroupUI(OptionsGroupUI):
             _("The units used in the Excellon file.")
         )
 
-        self.excellon_units_radio = RadioSet([{'label': _('INCH'), 'value': 'INCH'},
-                                              {'label': _('MM'), 'value': 'METRIC'}])
+        self.excellon_units_radio = RadioSet([{'label': _('Inch'), 'value': 'INCH'},
+                                              {'label': _('mm'), 'value': 'METRIC'}])
         self.excellon_units_radio.setToolTip(
             _("The units used in the Excellon file.")
         )

+ 2 - 2
appGUI/preferences/excellon/ExcellonGenPrefGroupUI.py

@@ -179,8 +179,8 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
               "therefore this parameter will be used.")
         )
 
-        self.excellon_units_radio = RadioSet([{'label': _('INCH'), 'value': 'INCH'},
-                                              {'label': _('MM'), 'value': 'METRIC'}])
+        self.excellon_units_radio = RadioSet([{'label': _('Inch'), 'value': 'INCH'},
+                                              {'label': _('mm'), 'value': 'METRIC'}])
         self.excellon_units_radio.setToolTip(
             _("This sets the units of Excellon files.\n"
               "Some Excellon files don't have an header\n"

+ 2 - 2
appGUI/preferences/excellon/ExcellonOptPrefGroupUI.py

@@ -30,7 +30,7 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
         self.decimals = decimals
 
         # ## Create CNC Job
-        self.cncjob_label = QtWidgets.QLabel('<b>%s</b>' % _('Create CNC Job'))
+        self.cncjob_label = QtWidgets.QLabel('<b>%s</b>' % _('Create CNCJob'))
         self.cncjob_label.setToolTip(
             _("Parameters used to create a CNC Job object\n"
               "for this drill object.")
@@ -84,7 +84,7 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
 
         self.mill_dia_entry = FCDoubleSpinner()
         self.mill_dia_entry.set_precision(self.decimals)
-        self.mill_dia_entry.set_range(0.0000, 9999.9999)
+        self.mill_dia_entry.set_range(0.0000, 10000.0000)
 
         grid2.addWidget(self.mill_dia_label, 2, 0)
         grid2.addWidget(self.mill_dia_entry, 2, 1)

+ 5 - 5
appGUI/preferences/general/GeneralAppPrefGroupUI.py

@@ -61,7 +61,7 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
         grid0.addWidget(self.precision_metric_entry, 1, 1)
 
         # Precision Inch
-        self.precision_inch_label = QtWidgets.QLabel('%s:' % _('Precision INCH'))
+        self.precision_inch_label = QtWidgets.QLabel('%s:' % _('Precision Inch'))
         self.precision_inch_label.setToolTip(
             _("The number of decimals used throughout the application\n"
               "when the set units are in INCH system.\n"
@@ -316,7 +316,7 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
         # Top Margin value
         self.tmargin_entry = FCDoubleSpinner()
         self.tmargin_entry.set_precision(self.decimals)
-        self.tmargin_entry.set_range(0.0000, 9999.9999)
+        self.tmargin_entry.set_range(0.0000, 10000.0000)
 
         self.tmargin_label = QtWidgets.QLabel('%s:' % _("Top Margin"))
         self.tmargin_label.setToolTip(
@@ -329,7 +329,7 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
         # Bottom Margin value
         self.bmargin_entry = FCDoubleSpinner()
         self.bmargin_entry.set_precision(self.decimals)
-        self.bmargin_entry.set_range(0.0000, 9999.9999)
+        self.bmargin_entry.set_range(0.0000, 10000.0000)
 
         self.bmargin_label = QtWidgets.QLabel('%s:' % _("Bottom Margin"))
         self.bmargin_label.setToolTip(
@@ -342,7 +342,7 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
         # Left Margin value
         self.lmargin_entry = FCDoubleSpinner()
         self.lmargin_entry.set_precision(self.decimals)
-        self.lmargin_entry.set_range(0.0000, 9999.9999)
+        self.lmargin_entry.set_range(0.0000, 10000.0000)
 
         self.lmargin_label = QtWidgets.QLabel('%s:' % _("Left Margin"))
         self.lmargin_label.setToolTip(
@@ -355,7 +355,7 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
         # Right Margin value
         self.rmargin_entry = FCDoubleSpinner()
         self.rmargin_entry.set_precision(self.decimals)
-        self.rmargin_entry.set_range(0.0000, 9999.9999)
+        self.rmargin_entry.set_range(0.0000, 10000.0000)
 
         self.rmargin_label = QtWidgets.QLabel('%s:' % _("Right Margin"))
         self.rmargin_label.setToolTip(

+ 1 - 141
appGUI/preferences/general/GeneralGUIPrefGroupUI.py

@@ -326,7 +326,7 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
         self.proj_color_entry.editingFinished.connect(self.on_proj_color_entry)
         self.proj_color_dis_entry.editingFinished.connect(self.on_proj_color_dis_entry)
 
-        self.layout_combo.activated.connect(self.on_layout)
+        self.layout_combo.activated.connect(self.app.on_layout)
 
     @staticmethod
     def handle_style(style):
@@ -407,143 +407,3 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
 
     def on_proj_color_dis_entry(self):
         self.app.defaults['global_proj_item_dis_color'] = self.proj_color_dis_entry.get_value()
-
-    def on_layout(self, index=None, lay=None):
-        """
-        Set the toolbars layout (location)
-
-        :param index:
-        :param lay:     Type of layout to be set on the toolbard
-        :return:        None
-        """
-
-        self.app.defaults.report_usage("on_layout()")
-        if lay:
-            current_layout = lay
-        else:
-            current_layout = self.layout_combo.get_value()
-
-        lay_settings = QSettings("Open Source", "FlatCAM")
-        lay_settings.setValue('layout', current_layout)
-
-        # This will write the setting to the platform specific storage.
-        del lay_settings
-
-        # first remove the toolbars:
-        try:
-            self.app.ui.removeToolBar(self.app.ui.toolbarfile)
-            self.app.ui.removeToolBar(self.app.ui.toolbaredit)
-            self.app.ui.removeToolBar(self.app.ui.toolbarview)
-            self.app.ui.removeToolBar(self.app.ui.toolbarshell)
-            self.app.ui.removeToolBar(self.app.ui.toolbartools)
-            self.app.ui.removeToolBar(self.app.ui.exc_edit_toolbar)
-            self.app.ui.removeToolBar(self.app.ui.geo_edit_toolbar)
-            self.app.ui.removeToolBar(self.app.ui.grb_edit_toolbar)
-            self.app.ui.removeToolBar(self.app.ui.toolbarshell)
-        except Exception:
-            pass
-
-        if current_layout == 'compact':
-            # ## TOOLBAR INSTALLATION # ##
-            self.app.ui.toolbarfile = QtWidgets.QToolBar('File Toolbar')
-            self.app.ui.toolbarfile.setObjectName('File_TB')
-            self.app.ui.addToolBar(Qt.LeftToolBarArea, self.app.ui.toolbarfile)
-
-            self.app.ui.toolbaredit = QtWidgets.QToolBar('Edit Toolbar')
-            self.app.ui.toolbaredit.setObjectName('Edit_TB')
-            self.app.ui.addToolBar(Qt.LeftToolBarArea, self.app.ui.toolbaredit)
-
-            self.app.ui.toolbarshell = QtWidgets.QToolBar('Shell Toolbar')
-            self.app.ui.toolbarshell.setObjectName('Shell_TB')
-            self.app.ui.addToolBar(Qt.LeftToolBarArea, self.app.ui.toolbarshell)
-
-            self.app.ui.toolbartools = QtWidgets.QToolBar('Tools Toolbar')
-            self.app.ui.toolbartools.setObjectName('Tools_TB')
-            self.app.ui.addToolBar(Qt.LeftToolBarArea, self.app.ui.toolbartools)
-
-            self.app.ui.geo_edit_toolbar = QtWidgets.QToolBar('Geometry Editor Toolbar')
-            # self.app.ui.geo_edit_toolbar.setVisible(False)
-            self.app.ui.geo_edit_toolbar.setObjectName('GeoEditor_TB')
-            self.app.ui.addToolBar(Qt.RightToolBarArea, self.app.ui.geo_edit_toolbar)
-
-            self.app.ui.toolbarview = QtWidgets.QToolBar('View Toolbar')
-            self.app.ui.toolbarview.setObjectName('View_TB')
-            self.app.ui.addToolBar(Qt.RightToolBarArea, self.app.ui.toolbarview)
-
-            self.app.ui.addToolBarBreak(area=Qt.RightToolBarArea)
-
-            self.app.ui.grb_edit_toolbar = QtWidgets.QToolBar('Gerber Editor Toolbar')
-            # self.app.ui.grb_edit_toolbar.setVisible(False)
-            self.app.ui.grb_edit_toolbar.setObjectName('GrbEditor_TB')
-            self.app.ui.addToolBar(Qt.RightToolBarArea, self.app.ui.grb_edit_toolbar)
-
-            self.app.ui.exc_edit_toolbar = QtWidgets.QToolBar('Excellon Editor Toolbar')
-            self.app.ui.exc_edit_toolbar.setObjectName('ExcEditor_TB')
-            self.app.ui.addToolBar(Qt.RightToolBarArea, self.app.ui.exc_edit_toolbar)
-
-        else:
-            # ## TOOLBAR INSTALLATION # ##
-            self.app.ui.toolbarfile = QtWidgets.QToolBar('File Toolbar')
-            self.app.ui.toolbarfile.setObjectName('File_TB')
-            self.app.ui.addToolBar(self.app.ui.toolbarfile)
-
-            self.app.ui.toolbaredit = QtWidgets.QToolBar('Edit Toolbar')
-            self.app.ui.toolbaredit.setObjectName('Edit_TB')
-            self.app.ui.addToolBar(self.app.ui.toolbaredit)
-
-            self.app.ui.toolbarview = QtWidgets.QToolBar('View Toolbar')
-            self.app.ui.toolbarview.setObjectName('View_TB')
-            self.app.ui.addToolBar(self.app.ui.toolbarview)
-
-            self.app.ui.toolbarshell = QtWidgets.QToolBar('Shell Toolbar')
-            self.app.ui.toolbarshell.setObjectName('Shell_TB')
-            self.app.ui.addToolBar(self.app.ui.toolbarshell)
-
-            self.app.ui.toolbartools = QtWidgets.QToolBar('Tools Toolbar')
-            self.app.ui.toolbartools.setObjectName('Tools_TB')
-            self.app.ui.addToolBar(self.app.ui.toolbartools)
-
-            self.app.ui.exc_edit_toolbar = QtWidgets.QToolBar('Excellon Editor Toolbar')
-            # self.app.ui.exc_edit_toolbar.setVisible(False)
-            self.app.ui.exc_edit_toolbar.setObjectName('ExcEditor_TB')
-            self.app.ui.addToolBar(self.app.ui.exc_edit_toolbar)
-
-            self.app.ui.addToolBarBreak()
-
-            self.app.ui.geo_edit_toolbar = QtWidgets.QToolBar('Geometry Editor Toolbar')
-            # self.app.ui.geo_edit_toolbar.setVisible(False)
-            self.app.ui.geo_edit_toolbar.setObjectName('GeoEditor_TB')
-            self.app.ui.addToolBar(self.app.ui.geo_edit_toolbar)
-
-            self.app.ui.grb_edit_toolbar = QtWidgets.QToolBar('Gerber Editor Toolbar')
-            # self.app.ui.grb_edit_toolbar.setVisible(False)
-            self.app.ui.grb_edit_toolbar.setObjectName('GrbEditor_TB')
-            self.app.ui.addToolBar(self.app.ui.grb_edit_toolbar)
-
-        if current_layout == 'minimal':
-            self.app.ui.toolbarview.setVisible(False)
-            self.app.ui.toolbarshell.setVisible(False)
-            self.app.ui.geo_edit_toolbar.setVisible(False)
-            self.app.ui.grb_edit_toolbar.setVisible(False)
-            self.app.ui.exc_edit_toolbar.setVisible(False)
-            self.app.ui.lock_toolbar(lock=True)
-
-        # add all the actions to the toolbars
-        self.app.ui.populate_toolbars()
-
-        try:
-            # reconnect all the signals to the toolbar actions
-            self.app.connect_toolbar_signals(ui=self.app.ui)
-        except Exception as e:
-            self.app.log.debug(
-                "appGUI.preferences.general.GeneralGUIPrefGroupUI.on_layout() - connect toolbar signals -> %s" % str(e))
-
-        self.app.ui.grid_snap_btn.setChecked(True)
-
-        self.app.ui.corner_snap_btn.setVisible(False)
-        self.app.ui.snap_magnet.setVisible(False)
-
-        self.app.ui.grid_gap_x_entry.setText(str(self.app.defaults["global_gridx"]))
-        self.app.ui.grid_gap_y_entry.setText(str(self.app.defaults["global_gridy"]))
-        self.app.ui.snap_max_dist_entry.setText(str(self.app.defaults["global_snap_max"]))
-        self.app.ui.grid_gap_link_cb.setChecked(True)

+ 113 - 16
appGUI/preferences/geometry/GeometryAdvOptPrefGroupUI.py

@@ -2,7 +2,7 @@ from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
 from appGUI.GUIElements import FCDoubleSpinner, FCCheckBox, RadioSet, FCLabel, NumericalEvalTupleEntry, \
-    NumericalEvalEntry
+    NumericalEvalEntry, FCComboBox2
 from appGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
@@ -31,9 +31,9 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
         # ------------------------------
         # ## Advanced Options
         # ------------------------------
-        self.geo_label = QtWidgets.QLabel('<b>%s:</b>' % _('Advanced Options'))
+        self.geo_label = FCLabel('<b>%s:</b>' % _('Advanced Options'))
         self.geo_label.setToolTip(
-            _("A list of Geometry advanced parameters.\n"
+            _("A list of advanced parameters.\n"
               "Those parameters are available only for\n"
               "Advanced App. Level.")
         )
@@ -43,7 +43,7 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
         self.layout.addLayout(grid1)
 
         # Toolchange X,Y
-        toolchange_xy_label = QtWidgets.QLabel('%s:' % _('Toolchange X-Y'))
+        toolchange_xy_label = FCLabel('%s:' % _('Toolchange X-Y'))
         toolchange_xy_label.setToolTip(
             _("Toolchange X,Y position.")
         )
@@ -53,7 +53,7 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
         grid1.addWidget(self.toolchangexy_entry, 1, 1)
 
         # Start move Z
-        startzlabel = QtWidgets.QLabel('%s:' % _('Start Z'))
+        startzlabel = FCLabel('%s:' % _('Start Z'))
         startzlabel.setToolTip(
             _("Height of the tool just after starting the work.\n"
               "Delete the value if you don't need this feature.")
@@ -64,7 +64,7 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
         grid1.addWidget(self.gstartz_entry, 2, 1)
 
         # Feedrate rapids
-        fr_rapid_label = QtWidgets.QLabel('%s:' % _('Feedrate Rapids'))
+        fr_rapid_label = FCLabel('%s:' % _('Feedrate Rapids'))
         fr_rapid_label.setToolTip(
             _("Cutting speed in the XY plane\n"
               "(in units per minute).\n"
@@ -73,7 +73,7 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
               "ignore for any other cases.")
         )
         self.feedrate_rapid_entry = FCDoubleSpinner()
-        self.feedrate_rapid_entry.set_range(0, 99999.9999)
+        self.feedrate_rapid_entry.set_range(0, 910000.0000)
         self.feedrate_rapid_entry.set_precision(self.decimals)
         self.feedrate_rapid_entry.setSingleStep(0.1)
         self.feedrate_rapid_entry.setWrapping(True)
@@ -105,7 +105,7 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
         grid1.addWidget(self.e_cut_entry, 5, 1)
 
         # Probe depth
-        self.pdepth_label = QtWidgets.QLabel('%s:' % _("Probe Z depth"))
+        self.pdepth_label = FCLabel('%s:' % _("Probe Z depth"))
         self.pdepth_label.setToolTip(
             _("The maximum depth that the probe is allowed\n"
               "to probe. Negative value, in current units.")
@@ -120,12 +120,12 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
         grid1.addWidget(self.pdepth_entry, 6, 1)
 
         # Probe feedrate
-        self.feedrate_probe_label = QtWidgets.QLabel('%s:' % _("Feedrate Probe"))
+        self.feedrate_probe_label = FCLabel('%s:' % _("Feedrate Probe"))
         self.feedrate_probe_label.setToolTip(
             _("The feedrate used while the probe is probing.")
         )
         self.feedrate_probe_entry = FCDoubleSpinner()
-        self.feedrate_probe_entry.set_range(0, 99999.9999)
+        self.feedrate_probe_entry.set_range(0, 910000.0000)
         self.feedrate_probe_entry.set_precision(self.decimals)
         self.feedrate_probe_entry.setSingleStep(0.1)
         self.feedrate_probe_entry.setWrapping(True)
@@ -134,7 +134,7 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
         grid1.addWidget(self.feedrate_probe_entry, 7, 1)
 
         # Spindle direction
-        spindle_dir_label = QtWidgets.QLabel('%s:' % _('Spindle direction'))
+        spindle_dir_label = FCLabel('%s:' % _('Spindle direction'))
         spindle_dir_label.setToolTip(
             _("This sets the direction that the spindle is rotating.\n"
               "It can be either:\n"
@@ -158,7 +158,7 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
         grid1.addWidget(self.fplunge_cb, 9, 0, 1, 2)
 
         # Size of trace segment on X axis
-        segx_label = QtWidgets.QLabel('%s:' % _("Segment X size"))
+        segx_label = FCLabel('%s:' % _("Segment X size"))
         segx_label.setToolTip(
             _("The size of the trace segment on the X axis.\n"
               "Useful for auto-leveling.\n"
@@ -174,7 +174,7 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
         grid1.addWidget(self.segx_entry, 10, 1)
 
         # Size of trace segment on Y axis
-        segy_label = QtWidgets.QLabel('%s:' % _("Segment Y size"))
+        segy_label = FCLabel('%s:' % _("Segment Y size"))
         segy_label.setToolTip(
             _("The size of the trace segment on the Y axis.\n"
               "Useful for auto-leveling.\n"
@@ -197,7 +197,7 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
         # -----------------------------
         # --- Area Exclusion ----------
         # -----------------------------
-        self.area_exc_label = QtWidgets.QLabel('<b>%s:</b>' % _('Area Exclusion'))
+        self.area_exc_label = FCLabel('<b>%s:</b>' % _('Area Exclusion'))
         self.area_exc_label.setToolTip(
             _("Area exclusion parameters.")
         )
@@ -215,7 +215,7 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
         grid1.addWidget(self.exclusion_cb, 14, 0, 1, 2)
 
         # Area Selection shape
-        self.area_shape_label = QtWidgets.QLabel('%s:' % _("Shape"))
+        self.area_shape_label = FCLabel('%s:' % _("Shape"))
         self.area_shape_label.setToolTip(
             _("The kind of selection shape used for area selection.")
         )
@@ -243,10 +243,107 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
         self.over_z_label.setToolTip(_("The height Z to which the tool will rise in order to avoid\n"
                                        "an interdiction area."))
         self.over_z_entry = FCDoubleSpinner()
-        self.over_z_entry.set_range(0.000, 9999.9999)
+        self.over_z_entry.set_range(0.000, 10000.0000)
         self.over_z_entry.set_precision(self.decimals)
 
         grid1.addWidget(self.over_z_label, 18, 0)
         grid1.addWidget(self.over_z_entry, 18, 1)
+        
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid1.addWidget(separator_line, 20, 0, 1, 2)
+        
+        # -----------------------------
+        # --- Area POLISH ----------
+        # -----------------------------
+        # Add Polish
+        self.polish_cb = FCCheckBox(label=_('Add Polish'))
+        self.polish_cb.setToolTip(_(
+            "Will add a Paint section at the end of the GCode.\n"
+            "A metallic brush will clean the material after milling."))
+        grid1.addWidget(self.polish_cb, 22, 0, 1, 2)
+
+        # Polish Tool Diameter
+        self.polish_dia_lbl = FCLabel('%s:' % _('Tool Dia'))
+        self.polish_dia_lbl.setToolTip(
+            _("Diameter for the polishing tool.")
+        )
+        self.polish_dia_entry = FCDoubleSpinner()
+        self.polish_dia_entry.set_precision(self.decimals)
+        self.polish_dia_entry.set_range(0.000, 10000.0000)
+
+        grid1.addWidget(self.polish_dia_lbl, 24, 0)
+        grid1.addWidget(self.polish_dia_entry, 24, 1)
+
+        # Polish Travel Z
+        self.polish_travelz_lbl = FCLabel('%s:' % _('Travel Z'))
+        self.polish_travelz_lbl.setToolTip(
+            _("Height of the tool when\n"
+              "moving without cutting.")
+        )
+        self.polish_travelz_entry = FCDoubleSpinner()
+        self.polish_travelz_entry.set_precision(self.decimals)
+        self.polish_travelz_entry.set_range(0.00000, 10000.00000)
+        self.polish_travelz_entry.setSingleStep(0.1)
+
+        grid1.addWidget(self.polish_travelz_lbl, 26, 0)
+        grid1.addWidget(self.polish_travelz_entry, 26, 1)
+
+        # Polish Pressure
+        self.polish_pressure_lbl = FCLabel('%s:' % _('Pressure'))
+        self.polish_pressure_lbl.setToolTip(
+            _("Negative value. The higher the absolute value\n"
+              "the stronger the pressure of the brush on the material.")
+        )
+        self.polish_pressure_entry = FCDoubleSpinner()
+        self.polish_pressure_entry.set_precision(self.decimals)
+        self.polish_pressure_entry.set_range(-10000.0000, 10000.0000)
+
+        grid1.addWidget(self.polish_pressure_lbl, 28, 0)
+        grid1.addWidget(self.polish_pressure_entry, 28, 1)
+
+        # Polish Margin
+        self.polish_margin_lbl = FCLabel('%s:' % _('Margin'))
+        self.polish_margin_lbl.setToolTip(
+            _("Bounding box margin.")
+        )
+        self.polish_margin_entry = FCDoubleSpinner()
+        self.polish_margin_entry.set_precision(self.decimals)
+        self.polish_margin_entry.set_range(-10000.0000, 10000.0000)
+
+        grid1.addWidget(self.polish_margin_lbl, 30, 0)
+        grid1.addWidget(self.polish_margin_entry, 30, 1)
+
+        # Polish Overlap
+        self.polish_over_lbl = FCLabel('%s:' % _('Overlap'))
+        self.polish_over_lbl.setToolTip(
+            _("How much (percentage) of the tool width to overlap each tool pass.")
+        )
+        self.polish_over_entry = FCDoubleSpinner(suffix='%')
+        self.polish_over_entry.set_precision(self.decimals)
+        self.polish_over_entry.setWrapping(True)
+        self.polish_over_entry.set_range(0.0000, 99.9999)
+        self.polish_over_entry.setSingleStep(0.1)
+
+        grid1.addWidget(self.polish_over_lbl, 32, 0)
+        grid1.addWidget(self.polish_over_entry, 32, 1)
+
+        # Polish Method
+        self.polish_method_lbl = FCLabel('%s:' % _('Method'))
+        self.polish_method_lbl.setToolTip(
+            _("Algorithm for polishing:\n"
+              "- Standard: Fixed step inwards.\n"
+              "- Seed-based: Outwards from seed.\n"
+              "- Line-based: Parallel lines.")
+        )
+
+        self.polish_method_combo = FCComboBox2()
+        self.polish_method_combo.addItems(
+            [_("Standard"), _("Seed"), _("Lines")]
+        )
+
+        grid1.addWidget(self.polish_method_lbl, 34, 0)
+        grid1.addWidget(self.polish_method_combo, 34, 1)
 
         self.layout.addStretch()

+ 12 - 11
appGUI/preferences/geometry/GeometryOptPrefGroupUI.py

@@ -32,7 +32,7 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
         # ------------------------------
         # ## Create CNC Job
         # ------------------------------
-        self.cncjob_label = QtWidgets.QLabel('<b>%s:</b>' % _('Create CNC Job'))
+        self.cncjob_label = QtWidgets.QLabel('<b>%s:</b>' % _('Create CNCJob'))
         self.cncjob_label.setToolTip(
             _("Create a CNC Job object\n"
               "tracing the contours of this\n"
@@ -54,9 +54,9 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
         self.cutz_entry = FCDoubleSpinner()
 
         if machinist_setting == 0:
-            self.cutz_entry.set_range(-9999.9999, 0.0000)
+            self.cutz_entry.set_range(-10000.0000, 0.0000)
         else:
-            self.cutz_entry.set_range(-9999.9999, 9999.9999)
+            self.cutz_entry.set_range(-10000.0000, 10000.0000)
 
         self.cutz_entry.set_precision(self.decimals)
         self.cutz_entry.setSingleStep(0.1)
@@ -107,9 +107,9 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
         self.travelz_entry = FCDoubleSpinner()
 
         if machinist_setting == 0:
-            self.travelz_entry.set_range(0.0001, 9999.9999)
+            self.travelz_entry.set_range(0.0001, 10000.0000)
         else:
-            self.travelz_entry.set_range(-9999.9999, 9999.9999)
+            self.travelz_entry.set_range(-10000.0000, 10000.0000)
 
         self.travelz_entry.set_precision(self.decimals)
         self.travelz_entry.setSingleStep(0.1)
@@ -139,9 +139,9 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
         self.toolchangez_entry = FCDoubleSpinner()
 
         if machinist_setting == 0:
-            self.toolchangez_entry.set_range(0.000, 9999.9999)
+            self.toolchangez_entry.set_range(0.000, 10000.0000)
         else:
-            self.toolchangez_entry.set_range(-9999.9999, 9999.9999)
+            self.toolchangez_entry.set_range(-10000.0000, 10000.0000)
 
         self.toolchangez_entry.set_precision(self.decimals)
         self.toolchangez_entry.setSingleStep(0.1)
@@ -159,9 +159,9 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
         self.endz_entry = FCDoubleSpinner()
 
         if machinist_setting == 0:
-            self.endz_entry.set_range(0.000, 9999.9999)
+            self.endz_entry.set_range(0.000, 10000.0000)
         else:
-            self.endz_entry.set_range(-9999.9999, 9999.9999)
+            self.endz_entry.set_range(-10000.0000, 10000.0000)
 
         self.endz_entry.set_precision(self.decimals)
         self.endz_entry.setSingleStep(0.1)
@@ -189,7 +189,7 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
               "plane in units per minute")
         )
         self.cncfeedrate_entry = FCDoubleSpinner()
-        self.cncfeedrate_entry.set_range(0, 99999.9999)
+        self.cncfeedrate_entry.set_range(0, 910000.0000)
         self.cncfeedrate_entry.set_precision(self.decimals)
         self.cncfeedrate_entry.setSingleStep(0.1)
         self.cncfeedrate_entry.setWrapping(True)
@@ -205,7 +205,7 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
               "It is called also Plunge.")
         )
         self.feedrate_z_entry = FCDoubleSpinner()
-        self.feedrate_z_entry.set_range(0, 99999.9999)
+        self.feedrate_z_entry.set_range(0, 910000.0000)
         self.feedrate_z_entry.set_precision(self.decimals)
         self.feedrate_z_entry.setSingleStep(0.1)
         self.feedrate_z_entry.setWrapping(True)
@@ -259,6 +259,7 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
         )
         self.pp_geometry_name_cb = FCComboBox()
         self.pp_geometry_name_cb.setFocusPolicy(Qt.StrongFocus)
+        self.pp_geometry_name_cb.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
 
         grid1.addWidget(pp_label, 13, 0)
         grid1.addWidget(self.pp_geometry_name_cb, 13, 1)

+ 1 - 1
appGUI/preferences/gerber/GerberAdvOptPrefGroupUI.py

@@ -30,7 +30,7 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI):
         # ## Advanced Gerber Parameters
         self.adv_param_label = QtWidgets.QLabel('<b>%s:</b>' % _('Advanced Options'))
         self.adv_param_label.setToolTip(
-            _("A list of Gerber advanced parameters.\n"
+            _("A list of advanced parameters.\n"
               "Those parameters are available only for\n"
               "Advanced App. Level.")
         )

+ 2 - 2
appGUI/preferences/gerber/GerberExpPrefGroupUI.py

@@ -44,8 +44,8 @@ class GerberExpPrefGroupUI(OptionsGroupUI):
             _("The units used in the Gerber file.")
         )
 
-        self.gerber_units_radio = RadioSet([{'label': _('INCH'), 'value': 'IN'},
-                                            {'label': _('MM'), 'value': 'MM'}])
+        self.gerber_units_radio = RadioSet([{'label': _('Inch'), 'value': 'IN'},
+                                            {'label': _('mm'), 'value': 'MM'}])
         self.gerber_units_radio.setToolTip(
             _("The units used in the Gerber file.")
         )

+ 2 - 2
appGUI/preferences/gerber/GerberGenPrefGroupUI.py

@@ -84,8 +84,8 @@ class GerberGenPrefGroupUI(OptionsGroupUI):
             _("The units used in the Gerber file.")
         )
 
-        self.gerber_units_radio = RadioSet([{'label': _('INCH'), 'value': 'IN'},
-                                            {'label': _('MM'), 'value': 'MM'}])
+        self.gerber_units_radio = RadioSet([{'label': _('Inch'), 'value': 'IN'},
+                                            {'label': _('mm'), 'value': 'MM'}])
         self.gerber_units_radio.setToolTip(
             _("The units used in the Gerber file.")
         )

+ 95 - 68
appGUI/preferences/tools/Tools2CThievingPrefGroupUI.py

@@ -1,7 +1,7 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from appGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet
+from appGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet, FCLabel
 from appGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
@@ -34,7 +34,7 @@ class Tools2CThievingPrefGroupUI(OptionsGroupUI):
         grid_lay.setColumnStretch(1, 1)
 
         # ## Parameters
-        self.cflabel = QtWidgets.QLabel('<b>%s</b>' % _('Parameters'))
+        self.cflabel = FCLabel('<b>%s</b>' % _('Parameters'))
         self.cflabel.setToolTip(
             _("A tool to generate a Copper Thieving that can be added\n"
               "to a selected Gerber file.")
@@ -42,7 +42,7 @@ class Tools2CThievingPrefGroupUI(OptionsGroupUI):
         grid_lay.addWidget(self.cflabel, 0, 0, 1, 2)
 
         # CIRCLE STEPS - to be used when buffering
-        self.circle_steps_lbl = QtWidgets.QLabel('%s:' % _("Circle Steps"))
+        self.circle_steps_lbl = FCLabel('%s:' % _("Circle Steps"))
         self.circle_steps_lbl.setToolTip(
             _("Number of steps (lines) used to interpolate circles.")
         )
@@ -50,11 +50,11 @@ class Tools2CThievingPrefGroupUI(OptionsGroupUI):
         self.circlesteps_entry = FCSpinner()
         self.circlesteps_entry.set_range(1, 9999)
 
-        grid_lay.addWidget(self.circle_steps_lbl, 1, 0)
-        grid_lay.addWidget(self.circlesteps_entry, 1, 1)
+        grid_lay.addWidget(self.circle_steps_lbl, 2, 0)
+        grid_lay.addWidget(self.circlesteps_entry, 2, 1)
 
         # CLEARANCE #
-        self.clearance_label = QtWidgets.QLabel('%s:' % _("Clearance"))
+        self.clearance_label = FCLabel('%s:' % _("Clearance"))
         self.clearance_label.setToolTip(
             _("This set the distance between the copper Thieving components\n"
               "(the polygon fill may be split in multiple polygons)\n"
@@ -65,11 +65,11 @@ class Tools2CThievingPrefGroupUI(OptionsGroupUI):
         self.clearance_entry.set_precision(self.decimals)
         self.clearance_entry.setSingleStep(0.1)
 
-        grid_lay.addWidget(self.clearance_label, 2, 0)
-        grid_lay.addWidget(self.clearance_entry, 2, 1)
+        grid_lay.addWidget(self.clearance_label, 4, 0)
+        grid_lay.addWidget(self.clearance_entry, 4, 1)
 
         # MARGIN #
-        self.margin_label = QtWidgets.QLabel('%s:' % _("Margin"))
+        self.margin_label = FCLabel('%s:' % _("Margin"))
         self.margin_label.setToolTip(
             _("Bounding box margin.")
         )
@@ -78,41 +78,54 @@ class Tools2CThievingPrefGroupUI(OptionsGroupUI):
         self.margin_entry.set_precision(self.decimals)
         self.margin_entry.setSingleStep(0.1)
 
-        grid_lay.addWidget(self.margin_label, 3, 0)
-        grid_lay.addWidget(self.margin_entry, 3, 1)
+        grid_lay.addWidget(self.margin_label, 6, 0)
+        grid_lay.addWidget(self.margin_entry, 6, 1)
 
+        # Area #
+        self.area_label = FCLabel('%s:' % _("Area"))
+        self.area_label.setToolTip(
+            _("Thieving areas with area less then this value will not be added.")
+        )
+        self.area_entry = FCDoubleSpinner()
+        self.area_entry.set_range(0.0, 10000.0000)
+        self.area_entry.set_precision(self.decimals)
+        self.area_entry.setSingleStep(0.1)
+
+        grid_lay.addWidget(self.area_label, 8, 0)
+        grid_lay.addWidget(self.area_entry, 8, 1)
+        
         # Reference #
         self.reference_radio = RadioSet([
             {'label': _('Itself'), 'value': 'itself'},
             {"label": _("Area Selection"), "value": "area"},
             {'label': _("Reference Object"), 'value': 'box'}
         ], orientation='vertical', stretch=False)
-        self.reference_label = QtWidgets.QLabel(_("Reference:"))
+        self.reference_label = FCLabel(_("Reference:"))
         self.reference_label.setToolTip(
-            _("- 'Itself' - the copper Thieving extent is based on the object extent.\n"
+            _("- 'Itself' - the copper thieving extent is based on the object extent.\n"
               "- 'Area Selection' - left mouse click to start selection of the area to be filled.\n"
               "- 'Reference Object' - will do copper thieving within the area specified by another object.")
         )
-        grid_lay.addWidget(self.reference_label, 4, 0)
-        grid_lay.addWidget(self.reference_radio, 4, 1)
+        grid_lay.addWidget(self.reference_label, 10, 0)
+        grid_lay.addWidget(self.reference_radio, 10, 1)
 
         # Bounding Box Type #
         self.bbox_type_radio = RadioSet([
             {'label': _('Rectangular'), 'value': 'rect'},
             {"label": _("Minimal"), "value": "min"}
         ], stretch=False)
-        self.bbox_type_label = QtWidgets.QLabel(_("Box Type:"))
+        self.bbox_type_label = FCLabel('%s:' % _("Box Type"))
         self.bbox_type_label.setToolTip(
             _("- 'Rectangular' - the bounding box will be of rectangular shape.\n"
               "- 'Minimal' - the bounding box will be the convex hull shape.")
         )
-        grid_lay.addWidget(self.bbox_type_label, 5, 0)
-        grid_lay.addWidget(self.bbox_type_radio, 5, 1)
+        grid_lay.addWidget(self.bbox_type_label, 12, 0)
+        grid_lay.addWidget(self.bbox_type_radio, 12, 1)
 
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid_lay.addWidget(separator_line, 6, 0, 1, 2)
+        grid_lay.addWidget(separator_line, 14, 0, 1, 2)
 
         # Fill Type
         self.fill_type_radio = RadioSet([
@@ -121,154 +134,168 @@ class Tools2CThievingPrefGroupUI(OptionsGroupUI):
             {"label": _("Squares Grid"), "value": "square"},
             {"label": _("Lines Grid"), "value": "line"}
         ], orientation='vertical', stretch=False)
-        self.fill_type_label = QtWidgets.QLabel(_("Fill Type:"))
+        self.fill_type_label = FCLabel(_("Fill Type:"))
         self.fill_type_label.setToolTip(
             _("- 'Solid' - copper thieving will be a solid polygon.\n"
               "- 'Dots Grid' - the empty area will be filled with a pattern of dots.\n"
               "- 'Squares Grid' - the empty area will be filled with a pattern of squares.\n"
               "- 'Lines Grid' - the empty area will be filled with a pattern of lines.")
         )
-        grid_lay.addWidget(self.fill_type_label, 7, 0)
-        grid_lay.addWidget(self.fill_type_radio, 7, 1)
+        grid_lay.addWidget(self.fill_type_label, 16, 0)
+        grid_lay.addWidget(self.fill_type_radio, 16, 1)
 
-        self.dots_label = QtWidgets.QLabel('<b>%s</b>:' % _("Dots Grid Parameters"))
-        grid_lay.addWidget(self.dots_label, 8, 0, 1, 2)
+        self.dots_label = FCLabel('<b>%s</b>:' % _("Dots Grid Parameters"))
+        grid_lay.addWidget(self.dots_label, 18, 0, 1, 2)
 
         # Dot diameter #
-        self.dotdia_label = QtWidgets.QLabel('%s:' % _("Dia"))
+        self.dotdia_label = FCLabel('%s:' % _("Dia"))
         self.dotdia_label.setToolTip(
             _("Dot diameter in Dots Grid.")
         )
         self.dot_dia_entry = FCDoubleSpinner()
-        self.dot_dia_entry.set_range(0.0, 9999.9999)
+        self.dot_dia_entry.set_range(0.0, 10000.0000)
         self.dot_dia_entry.set_precision(self.decimals)
         self.dot_dia_entry.setSingleStep(0.1)
 
-        grid_lay.addWidget(self.dotdia_label, 9, 0)
-        grid_lay.addWidget(self.dot_dia_entry, 9, 1)
+        grid_lay.addWidget(self.dotdia_label, 20, 0)
+        grid_lay.addWidget(self.dot_dia_entry, 20, 1)
 
         # Dot spacing #
-        self.dotspacing_label = QtWidgets.QLabel('%s:' % _("Spacing"))
+        self.dotspacing_label = FCLabel('%s:' % _("Spacing"))
         self.dotspacing_label.setToolTip(
             _("Distance between each two dots in Dots Grid.")
         )
         self.dot_spacing_entry = FCDoubleSpinner()
-        self.dot_spacing_entry.set_range(0.0, 9999.9999)
+        self.dot_spacing_entry.set_range(0.0, 10000.0000)
         self.dot_spacing_entry.set_precision(self.decimals)
         self.dot_spacing_entry.setSingleStep(0.1)
 
-        grid_lay.addWidget(self.dotspacing_label, 10, 0)
-        grid_lay.addWidget(self.dot_spacing_entry, 10, 1)
+        grid_lay.addWidget(self.dotspacing_label, 22, 0)
+        grid_lay.addWidget(self.dot_spacing_entry, 22, 1)
 
-        self.squares_label = QtWidgets.QLabel('<b>%s</b>:' % _("Squares Grid Parameters"))
-        grid_lay.addWidget(self.squares_label, 11, 0, 1, 2)
+        self.squares_label = FCLabel('<b>%s</b>:' % _("Squares Grid Parameters"))
+        grid_lay.addWidget(self.squares_label, 24, 0, 1, 2)
 
         # Square Size #
-        self.square_size_label = QtWidgets.QLabel('%s:' % _("Size"))
+        self.square_size_label = FCLabel('%s:' % _("Size"))
         self.square_size_label.setToolTip(
             _("Square side size in Squares Grid.")
         )
         self.square_size_entry = FCDoubleSpinner()
-        self.square_size_entry.set_range(0.0, 9999.9999)
+        self.square_size_entry.set_range(0.0, 10000.0000)
         self.square_size_entry.set_precision(self.decimals)
         self.square_size_entry.setSingleStep(0.1)
 
-        grid_lay.addWidget(self.square_size_label, 12, 0)
-        grid_lay.addWidget(self.square_size_entry, 12, 1)
+        grid_lay.addWidget(self.square_size_label, 26, 0)
+        grid_lay.addWidget(self.square_size_entry, 26, 1)
 
         # Squares spacing #
-        self.squares_spacing_label = QtWidgets.QLabel('%s:' % _("Spacing"))
+        self.squares_spacing_label = FCLabel('%s:' % _("Spacing"))
         self.squares_spacing_label.setToolTip(
             _("Distance between each two squares in Squares Grid.")
         )
         self.squares_spacing_entry = FCDoubleSpinner()
-        self.squares_spacing_entry.set_range(0.0, 9999.9999)
+        self.squares_spacing_entry.set_range(0.0, 10000.0000)
         self.squares_spacing_entry.set_precision(self.decimals)
         self.squares_spacing_entry.setSingleStep(0.1)
 
-        grid_lay.addWidget(self.squares_spacing_label, 13, 0)
-        grid_lay.addWidget(self.squares_spacing_entry, 13, 1)
+        grid_lay.addWidget(self.squares_spacing_label, 28, 0)
+        grid_lay.addWidget(self.squares_spacing_entry, 28, 1)
 
-        self.lines_label = QtWidgets.QLabel('<b>%s</b>:' % _("Lines Grid Parameters"))
-        grid_lay.addWidget(self.lines_label, 14, 0, 1, 2)
+        self.lines_label = FCLabel('<b>%s</b>:' % _("Lines Grid Parameters"))
+        grid_lay.addWidget(self.lines_label, 30, 0, 1, 2)
 
         # Square Size #
-        self.line_size_label = QtWidgets.QLabel('%s:' % _("Size"))
+        self.line_size_label = FCLabel('%s:' % _("Size"))
         self.line_size_label.setToolTip(
             _("Line thickness size in Lines Grid.")
         )
         self.line_size_entry = FCDoubleSpinner()
-        self.line_size_entry.set_range(0.0, 9999.9999)
+        self.line_size_entry.set_range(0.0, 10000.0000)
         self.line_size_entry.set_precision(self.decimals)
         self.line_size_entry.setSingleStep(0.1)
 
-        grid_lay.addWidget(self.line_size_label, 15, 0)
-        grid_lay.addWidget(self.line_size_entry, 15, 1)
+        grid_lay.addWidget(self.line_size_label, 32, 0)
+        grid_lay.addWidget(self.line_size_entry, 32, 1)
 
         # Lines spacing #
-        self.lines_spacing_label = QtWidgets.QLabel('%s:' % _("Spacing"))
+        self.lines_spacing_label = FCLabel('%s:' % _("Spacing"))
         self.lines_spacing_label.setToolTip(
             _("Distance between each two lines in Lines Grid.")
         )
         self.lines_spacing_entry = FCDoubleSpinner()
-        self.lines_spacing_entry.set_range(0.0, 9999.9999)
+        self.lines_spacing_entry.set_range(0.0, 10000.0000)
         self.lines_spacing_entry.set_precision(self.decimals)
         self.lines_spacing_entry.setSingleStep(0.1)
 
-        grid_lay.addWidget(self.lines_spacing_label, 16, 0)
-        grid_lay.addWidget(self.lines_spacing_entry, 16, 1)
+        grid_lay.addWidget(self.lines_spacing_label, 34, 0)
+        grid_lay.addWidget(self.lines_spacing_entry, 34, 1)
 
-        self.robber_bar_label = QtWidgets.QLabel('<b>%s</b>' % _('Robber Bar Parameters'))
+        self.robber_bar_label = FCLabel('<b>%s</b>' % _('Robber Bar Parameters'))
         self.robber_bar_label.setToolTip(
             _("Parameters used for the robber bar.\n"
               "Robber bar = copper border to help in pattern hole plating.")
         )
-        grid_lay.addWidget(self.robber_bar_label, 17, 0, 1, 2)
+        grid_lay.addWidget(self.robber_bar_label, 36, 0, 1, 2)
 
         # ROBBER BAR MARGIN #
-        self.rb_margin_label = QtWidgets.QLabel('%s:' % _("Margin"))
+        self.rb_margin_label = FCLabel('%s:' % _("Margin"))
         self.rb_margin_label.setToolTip(
             _("Bounding box margin for robber bar.")
         )
         self.rb_margin_entry = FCDoubleSpinner()
-        self.rb_margin_entry.set_range(-9999.9999, 9999.9999)
+        self.rb_margin_entry.set_range(-10000.0000, 10000.0000)
         self.rb_margin_entry.set_precision(self.decimals)
         self.rb_margin_entry.setSingleStep(0.1)
 
-        grid_lay.addWidget(self.rb_margin_label, 18, 0)
-        grid_lay.addWidget(self.rb_margin_entry, 18, 1)
+        grid_lay.addWidget(self.rb_margin_label, 38, 0)
+        grid_lay.addWidget(self.rb_margin_entry, 38, 1)
 
         # THICKNESS #
-        self.rb_thickness_label = QtWidgets.QLabel('%s:' % _("Thickness"))
+        self.rb_thickness_label = FCLabel('%s:' % _("Thickness"))
         self.rb_thickness_label.setToolTip(
             _("The robber bar thickness.")
         )
         self.rb_thickness_entry = FCDoubleSpinner()
-        self.rb_thickness_entry.set_range(0.0000, 9999.9999)
+        self.rb_thickness_entry.set_range(0.0000, 10000.0000)
         self.rb_thickness_entry.set_precision(self.decimals)
         self.rb_thickness_entry.setSingleStep(0.1)
 
-        grid_lay.addWidget(self.rb_thickness_label, 19, 0)
-        grid_lay.addWidget(self.rb_thickness_entry, 19, 1)
+        grid_lay.addWidget(self.rb_thickness_label, 40, 0)
+        grid_lay.addWidget(self.rb_thickness_entry, 40, 1)
 
-        self.patern_mask_label = QtWidgets.QLabel('<b>%s</b>' % _('Pattern Plating Mask'))
+        self.patern_mask_label = FCLabel('<b>%s</b>' % _('Pattern Plating Mask'))
         self.patern_mask_label.setToolTip(
             _("Generate a mask for pattern plating.")
         )
-        grid_lay.addWidget(self.patern_mask_label, 20, 0, 1, 2)
+        grid_lay.addWidget(self.patern_mask_label, 42, 0, 1, 2)
 
         # Openings CLEARANCE #
-        self.clearance_ppm_label = QtWidgets.QLabel('%s:' % _("Clearance"))
+        self.clearance_ppm_label = FCLabel('%s:' % _("Clearance"))
         self.clearance_ppm_label.setToolTip(
             _("The distance between the possible copper thieving elements\n"
               "and/or robber bar and the actual openings in the mask.")
         )
         self.clearance_ppm_entry = FCDoubleSpinner()
-        self.clearance_ppm_entry.set_range(-9999.9999, 9999.9999)
+        self.clearance_ppm_entry.set_range(-10000.0000, 10000.0000)
         self.clearance_ppm_entry.set_precision(self.decimals)
         self.clearance_ppm_entry.setSingleStep(0.1)
 
-        grid_lay.addWidget(self.clearance_ppm_label, 21, 0)
-        grid_lay.addWidget(self.clearance_ppm_entry, 21, 1)
+        grid_lay.addWidget(self.clearance_ppm_label, 44, 0)
+        grid_lay.addWidget(self.clearance_ppm_entry, 44, 1)
+
+        # Include geometry
+        self.ppm_choice_label = FCLabel('%s:' % _("Add"))
+        self.ppm_choice_label.setToolTip(
+            _("Choose which additional geometry to include, if available.")
+        )
+        self.ppm_choice_radio = RadioSet([
+            {"label": _("Both"), "value": "b"},
+            {'label': _('Thieving'), 'value': 't'},
+            {"label": _("Robber bar"), "value": "r"},
+            {"label": _("None"), "value": "n"}
+        ], orientation='vertical', stretch=False)
+        grid_lay.addWidget(self.ppm_choice_label, 46, 0)
+        grid_lay.addWidget(self.ppm_choice_radio, 46, 1)
 
         self.layout.addStretch()

+ 5 - 5
appGUI/preferences/tools/Tools2CalPrefGroupUI.py

@@ -64,7 +64,7 @@ class Tools2CalPrefGroupUI(OptionsGroupUI):
         )
 
         self.travelz_entry = FCDoubleSpinner()
-        self.travelz_entry.set_range(-9999.9999, 9999.9999)
+        self.travelz_entry.set_range(-10000.0000, 10000.0000)
         self.travelz_entry.set_precision(self.decimals)
         self.travelz_entry.setSingleStep(0.1)
 
@@ -78,7 +78,7 @@ class Tools2CalPrefGroupUI(OptionsGroupUI):
         )
 
         self.verz_entry = FCDoubleSpinner()
-        self.verz_entry.set_range(-9999.9999, 9999.9999)
+        self.verz_entry.set_range(-10000.0000, 10000.0000)
         self.verz_entry.set_precision(self.decimals)
         self.verz_entry.setSingleStep(0.1)
 
@@ -101,7 +101,7 @@ class Tools2CalPrefGroupUI(OptionsGroupUI):
         )
 
         self.toolchangez_entry = FCDoubleSpinner()
-        self.toolchangez_entry.set_range(0.0000, 9999.9999)
+        self.toolchangez_entry.set_range(0.0000, 10000.0000)
         self.toolchangez_entry.set_precision(self.decimals)
         self.toolchangez_entry.setSingleStep(0.1)
 
@@ -128,8 +128,8 @@ class Tools2CalPrefGroupUI(OptionsGroupUI):
               "- top-left -> the user will align the PCB vertically\n"
               "- bottom-right -> the user will align the PCB horizontally")
         )
-        self.second_point_radio = RadioSet([{'label': _('Top-Left'), 'value': 'tl'},
-                                            {'label': _('Bottom-Right'), 'value': 'br'}],
+        self.second_point_radio = RadioSet([{'label': _('Top Left'), 'value': 'tl'},
+                                            {'label': _('Bottom Right'), 'value': 'br'}],
                                            orientation='vertical')
 
         grid_lay.addWidget(second_point_lbl, 8, 0)

+ 6 - 6
appGUI/preferences/tools/Tools2EDrillsPrefGroupUI.py

@@ -126,7 +126,7 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI):
         # Diameter value
         self.dia_entry = FCDoubleSpinner()
         self.dia_entry.set_precision(self.decimals)
-        self.dia_entry.set_range(0.0000, 9999.9999)
+        self.dia_entry.set_range(0.0000, 10000.0000)
 
         self.dia_label = QtWidgets.QLabel('%s:' % _("Value"))
         self.dia_label.setToolTip(
@@ -153,7 +153,7 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI):
 
         self.circular_ring_entry = FCDoubleSpinner()
         self.circular_ring_entry.set_precision(self.decimals)
-        self.circular_ring_entry.set_range(0.0000, 9999.9999)
+        self.circular_ring_entry.set_range(0.0000, 10000.0000)
 
         grid_lay.addWidget(self.circular_ring_label, 14, 0)
         grid_lay.addWidget(self.circular_ring_entry, 14, 1)
@@ -166,7 +166,7 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI):
 
         self.oblong_ring_entry = FCDoubleSpinner()
         self.oblong_ring_entry.set_precision(self.decimals)
-        self.oblong_ring_entry.set_range(0.0000, 9999.9999)
+        self.oblong_ring_entry.set_range(0.0000, 10000.0000)
 
         grid_lay.addWidget(self.oblong_ring_label, 15, 0)
         grid_lay.addWidget(self.oblong_ring_entry, 15, 1)
@@ -179,7 +179,7 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI):
 
         self.square_ring_entry = FCDoubleSpinner()
         self.square_ring_entry.set_precision(self.decimals)
-        self.square_ring_entry.set_range(0.0000, 9999.9999)
+        self.square_ring_entry.set_range(0.0000, 10000.0000)
 
         grid_lay.addWidget(self.square_ring_label, 16, 0)
         grid_lay.addWidget(self.square_ring_entry, 16, 1)
@@ -192,7 +192,7 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI):
 
         self.rectangular_ring_entry = FCDoubleSpinner()
         self.rectangular_ring_entry.set_precision(self.decimals)
-        self.rectangular_ring_entry.set_range(0.0000, 9999.9999)
+        self.rectangular_ring_entry.set_range(0.0000, 10000.0000)
 
         grid_lay.addWidget(self.rectangular_ring_label, 17, 0)
         grid_lay.addWidget(self.rectangular_ring_entry, 17, 1)
@@ -205,7 +205,7 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI):
 
         self.other_ring_entry = FCDoubleSpinner()
         self.other_ring_entry.set_precision(self.decimals)
-        self.other_ring_entry.set_range(0.0000, 9999.9999)
+        self.other_ring_entry.set_range(0.0000, 10000.0000)
 
         grid_lay.addWidget(self.other_ring_label, 18, 0)
         grid_lay.addWidget(self.other_ring_entry, 18, 1)

+ 3 - 3
appGUI/preferences/tools/Tools2FiducialsPrefGroupUI.py

@@ -61,7 +61,7 @@ class Tools2FiducialsPrefGroupUI(OptionsGroupUI):
             _("Bounding box margin.")
         )
         self.margin_entry = FCDoubleSpinner()
-        self.margin_entry.set_range(-9999.9999, 9999.9999)
+        self.margin_entry.set_range(-10000.0000, 10000.0000)
         self.margin_entry.set_precision(self.decimals)
         self.margin_entry.setSingleStep(0.1)
 
@@ -73,7 +73,7 @@ class Tools2FiducialsPrefGroupUI(OptionsGroupUI):
             {'label': _('Auto'), 'value': 'auto'},
             {"label": _("Manual"), "value": "manual"}
         ], stretch=False)
-        self.mode_label = QtWidgets.QLabel(_("Mode:"))
+        self.mode_label = QtWidgets.QLabel('%s:' % _("Mode"))
         self.mode_label.setToolTip(
             _("- 'Auto' - automatic placement of fiducials in the corners of the bounding box.\n"
               "- 'Manual' - manual placement of fiducials.")
@@ -125,7 +125,7 @@ class Tools2FiducialsPrefGroupUI(OptionsGroupUI):
             _("Bounding box margin.")
         )
         self.line_thickness_entry = FCDoubleSpinner()
-        self.line_thickness_entry.set_range(0.00001, 9999.9999)
+        self.line_thickness_entry.set_range(0.00001, 10000.0000)
         self.line_thickness_entry.set_precision(self.decimals)
         self.line_thickness_entry.setSingleStep(0.1)
 

+ 1 - 1
appGUI/preferences/tools/Tools2InvertPrefGroupUI.py

@@ -49,7 +49,7 @@ class Tools2InvertPrefGroupUI(OptionsGroupUI):
         )
         self.margin_entry = FCDoubleSpinner()
         self.margin_entry.set_precision(self.decimals)
-        self.margin_entry.set_range(0.0000, 9999.9999)
+        self.margin_entry.set_range(0.0000, 10000.0000)
         self.margin_entry.setObjectName(_("Margin"))
 
         grid0.addWidget(self.margin_label, 2, 0, 1, 2)

+ 6 - 6
appGUI/preferences/tools/Tools2PunchGerberPrefGroupUI.py

@@ -128,7 +128,7 @@ class Tools2PunchGerberPrefGroupUI(OptionsGroupUI):
         # Diameter value
         self.dia_entry = FCDoubleSpinner()
         self.dia_entry.set_precision(self.decimals)
-        self.dia_entry.set_range(0.0000, 9999.9999)
+        self.dia_entry.set_range(0.0000, 10000.0000)
 
         self.dia_label = QtWidgets.QLabel('%s:' % _("Value"))
         self.dia_label.setToolTip(
@@ -155,7 +155,7 @@ class Tools2PunchGerberPrefGroupUI(OptionsGroupUI):
 
         self.circular_ring_entry = FCDoubleSpinner()
         self.circular_ring_entry.set_precision(self.decimals)
-        self.circular_ring_entry.set_range(0.0000, 9999.9999)
+        self.circular_ring_entry.set_range(0.0000, 10000.0000)
 
         grid_lay.addWidget(self.circular_ring_label, 14, 0)
         grid_lay.addWidget(self.circular_ring_entry, 14, 1)
@@ -168,7 +168,7 @@ class Tools2PunchGerberPrefGroupUI(OptionsGroupUI):
 
         self.oblong_ring_entry = FCDoubleSpinner()
         self.oblong_ring_entry.set_precision(self.decimals)
-        self.oblong_ring_entry.set_range(0.0000, 9999.9999)
+        self.oblong_ring_entry.set_range(0.0000, 10000.0000)
 
         grid_lay.addWidget(self.oblong_ring_label, 15, 0)
         grid_lay.addWidget(self.oblong_ring_entry, 15, 1)
@@ -181,7 +181,7 @@ class Tools2PunchGerberPrefGroupUI(OptionsGroupUI):
 
         self.square_ring_entry = FCDoubleSpinner()
         self.square_ring_entry.set_precision(self.decimals)
-        self.square_ring_entry.set_range(0.0000, 9999.9999)
+        self.square_ring_entry.set_range(0.0000, 10000.0000)
 
         grid_lay.addWidget(self.square_ring_label, 16, 0)
         grid_lay.addWidget(self.square_ring_entry, 16, 1)
@@ -194,7 +194,7 @@ class Tools2PunchGerberPrefGroupUI(OptionsGroupUI):
 
         self.rectangular_ring_entry = FCDoubleSpinner()
         self.rectangular_ring_entry.set_precision(self.decimals)
-        self.rectangular_ring_entry.set_range(0.0000, 9999.9999)
+        self.rectangular_ring_entry.set_range(0.0000, 10000.0000)
 
         grid_lay.addWidget(self.rectangular_ring_label, 17, 0)
         grid_lay.addWidget(self.rectangular_ring_entry, 17, 1)
@@ -207,7 +207,7 @@ class Tools2PunchGerberPrefGroupUI(OptionsGroupUI):
 
         self.other_ring_entry = FCDoubleSpinner()
         self.other_ring_entry.set_precision(self.decimals)
-        self.other_ring_entry.set_range(0.0000, 9999.9999)
+        self.other_ring_entry.set_range(0.0000, 10000.0000)
 
         grid_lay.addWidget(self.other_ring_label, 18, 0)
         grid_lay.addWidget(self.other_ring_entry, 18, 1)

+ 4 - 4
appGUI/preferences/tools/Tools2sidedPrefGroupUI.py

@@ -24,7 +24,7 @@ class Tools2sidedPrefGroupUI(OptionsGroupUI):
         # OptionsGroupUI.__init__(self, "2sided Tool Options", parent=parent)
         super(Tools2sidedPrefGroupUI, self).__init__(self, parent=parent)
 
-        self.setTitle(str(_("2Sided Tool Options")))
+        self.setTitle(str(_("2-Sided Tool Options")))
         self.decimals = decimals
 
         # ## Board cuttout
@@ -40,11 +40,11 @@ class Tools2sidedPrefGroupUI(OptionsGroupUI):
 
         # ## Drill diameter for alignment holes
         self.drill_dia_entry = FCDoubleSpinner()
-        self.drill_dia_entry.set_range(0.000001, 9999.9999)
+        self.drill_dia_entry.set_range(0.000001, 10000.0000)
         self.drill_dia_entry.set_precision(self.decimals)
         self.drill_dia_entry.setSingleStep(0.1)
 
-        self.dd_label = QtWidgets.QLabel('%s:' % _("Drill dia"))
+        self.dd_label = QtWidgets.QLabel('%s:' % _("Drill Dia"))
         self.dd_label.setToolTip(
             _("Diameter of the drill for the "
               "alignment holes.")
@@ -66,7 +66,7 @@ class Tools2sidedPrefGroupUI(OptionsGroupUI):
         # ## Axis
         self.mirror_axis_radio = RadioSet([{'label': 'X', 'value': 'X'},
                                            {'label': 'Y', 'value': 'Y'}])
-        self.mirax_label = QtWidgets.QLabel(_("Mirror Axis:"))
+        self.mirax_label = QtWidgets.QLabel('%s:' % _("Mirror Axis"))
         self.mirax_label.setToolTip(
             _("Mirror vertically (X) or horizontally (Y).")
         )

+ 40 - 29
appGUI/preferences/tools/ToolsCalculatorsPrefGroupUI.py

@@ -1,7 +1,7 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from appGUI.GUIElements import FCDoubleSpinner
+from appGUI.GUIElements import FCDoubleSpinner, FCLabel
 from appGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
@@ -28,7 +28,7 @@ class ToolsCalculatorsPrefGroupUI(OptionsGroupUI):
         self.decimals = decimals
 
         # ## V-shape Calculator Tool
-        self.vshape_tool_label = QtWidgets.QLabel("<b>%s:</b>" % _("V-Shape Tool Calculator"))
+        self.vshape_tool_label = FCLabel("<b>%s:</b>" % _("V-Shape Tool Calculator"))
         self.vshape_tool_label.setToolTip(
             _("Calculate the tool diameter for a given V-shape tool,\n"
               "having the tip diameter, tip angle and\n"
@@ -43,11 +43,11 @@ class ToolsCalculatorsPrefGroupUI(OptionsGroupUI):
 
         # ## Tip Diameter
         self.tip_dia_entry = FCDoubleSpinner()
-        self.tip_dia_entry.set_range(0.000001, 9999.9999)
+        self.tip_dia_entry.set_range(0.000001, 10000.0000)
         self.tip_dia_entry.set_precision(self.decimals)
         self.tip_dia_entry.setSingleStep(0.1)
 
-        self.tip_dia_label = QtWidgets.QLabel('%s:' % _("Tip Diameter"))
+        self.tip_dia_label = FCLabel('%s:' % _("Tip Diameter"))
         self.tip_dia_label.setToolTip(
             _("This is the tool tip diameter.\n"
               "It is specified by manufacturer.")
@@ -61,30 +61,30 @@ class ToolsCalculatorsPrefGroupUI(OptionsGroupUI):
         self.tip_angle_entry.set_precision(self.decimals)
         self.tip_angle_entry.setSingleStep(5)
 
-        self.tip_angle_label = QtWidgets.QLabel('%s:' % _("Tip Angle"))
+        self.tip_angle_label = FCLabel('%s:' % _("Tip Angle"))
         self.tip_angle_label.setToolTip(
             _("This is the angle on the tip of the tool.\n"
               "It is specified by manufacturer.")
         )
-        grid0.addWidget(self.tip_angle_label, 1, 0)
-        grid0.addWidget(self.tip_angle_entry, 1, 1)
+        grid0.addWidget(self.tip_angle_label, 2, 0)
+        grid0.addWidget(self.tip_angle_entry, 2, 1)
 
         # ## Depth-of-cut Cut Z
         self.cut_z_entry = FCDoubleSpinner()
-        self.cut_z_entry.set_range(-9999.9999, 0.0000)
+        self.cut_z_entry.set_range(-10000.0000, 0.0000)
         self.cut_z_entry.set_precision(self.decimals)
         self.cut_z_entry.setSingleStep(0.01)
 
-        self.cut_z_label = QtWidgets.QLabel('%s:' % _("Cut Z"))
+        self.cut_z_label = FCLabel('%s:' % _("Cut Z"))
         self.cut_z_label.setToolTip(
             _("This is depth to cut into material.\n"
               "In the CNCJob object it is the CutZ parameter.")
         )
-        grid0.addWidget(self.cut_z_label, 2, 0)
-        grid0.addWidget(self.cut_z_entry, 2, 1)
+        grid0.addWidget(self.cut_z_label, 4, 0)
+        grid0.addWidget(self.cut_z_entry, 4, 1)
 
         # ## Electroplating Calculator Tool
-        self.plate_title_label = QtWidgets.QLabel("<b>%s:</b>" % _("ElectroPlating Calculator"))
+        self.plate_title_label = FCLabel("<b>%s:</b>" % _("ElectroPlating Calculator"))
         self.plate_title_label.setToolTip(
             _("This calculator is useful for those who plate the via/pad/drill holes,\n"
               "using a method like graphite ink or calcium hypophosphite ink or palladium chloride.")
@@ -93,50 +93,61 @@ class ToolsCalculatorsPrefGroupUI(OptionsGroupUI):
 
         # ## PCB Length
         self.pcblength_entry = FCDoubleSpinner()
-        self.pcblength_entry.set_range(0.000001, 9999.9999)
+        self.pcblength_entry.set_range(0.000001, 10000.0000)
         self.pcblength_entry.set_precision(self.decimals)
         self.pcblength_entry.setSingleStep(0.1)
 
-        self.pcblengthlabel = QtWidgets.QLabel('%s:' % _("Board Length"))
+        self.pcblengthlabel = FCLabel('%s:' % _("Board Length"))
 
         self.pcblengthlabel.setToolTip(_('This is the board length. In centimeters.'))
-        grid0.addWidget(self.pcblengthlabel, 4, 0)
-        grid0.addWidget(self.pcblength_entry, 4, 1)
+        grid0.addWidget(self.pcblengthlabel, 6, 0)
+        grid0.addWidget(self.pcblength_entry, 6, 1)
 
         # ## PCB Width
         self.pcbwidth_entry = FCDoubleSpinner()
-        self.pcbwidth_entry.set_range(0.000001, 9999.9999)
+        self.pcbwidth_entry.set_range(0.000001, 10000.0000)
         self.pcbwidth_entry.set_precision(self.decimals)
         self.pcbwidth_entry.setSingleStep(0.1)
 
-        self.pcbwidthlabel = QtWidgets.QLabel('%s:' % _("Board Width"))
+        self.pcbwidthlabel = FCLabel('%s:' % _("Board Width"))
 
         self.pcbwidthlabel.setToolTip(_('This is the board width.In centimeters.'))
-        grid0.addWidget(self.pcbwidthlabel, 5, 0)
-        grid0.addWidget(self.pcbwidth_entry, 5, 1)
-
+        grid0.addWidget(self.pcbwidthlabel, 8, 0)
+        grid0.addWidget(self.pcbwidth_entry, 8, 1)
+        
+        # AREA
+        self.area_label = FCLabel('%s:' % _("Area"))
+        self.area_label.setToolTip(_('This is the board area.'))
+        self.area_entry = FCDoubleSpinner()
+        self.area_entry.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
+        self.area_entry.set_precision(self.decimals)
+        self.area_entry.set_range(0.0, 10000.0000)
+        
+        grid0.addWidget(self.area_label, 10, 0)
+        grid0.addWidget(self.area_entry, 10, 1)
+        
         # ## Current Density
-        self.cdensity_label = QtWidgets.QLabel('%s:' % _("Current Density"))
+        self.cdensity_label = FCLabel('%s:' % _("Current Density"))
         self.cdensity_entry = FCDoubleSpinner()
-        self.cdensity_entry.set_range(0.000001, 9999.9999)
+        self.cdensity_entry.set_range(0.000001, 10000.0000)
         self.cdensity_entry.set_precision(self.decimals)
         self.cdensity_entry.setSingleStep(0.1)
 
         self.cdensity_label.setToolTip(_("Current density to pass through the board. \n"
                                          "In Amps per Square Feet ASF."))
-        grid0.addWidget(self.cdensity_label, 6, 0)
-        grid0.addWidget(self.cdensity_entry, 6, 1)
+        grid0.addWidget(self.cdensity_label, 12, 0)
+        grid0.addWidget(self.cdensity_entry, 12, 1)
 
         # ## PCB Copper Growth
-        self.growth_label = QtWidgets.QLabel('%s:' % _("Copper Growth"))
+        self.growth_label = FCLabel('%s:' % _("Copper Growth"))
         self.growth_entry = FCDoubleSpinner()
-        self.growth_entry.set_range(0.000001, 9999.9999)
+        self.growth_entry.set_range(0.000001, 10000.0000)
         self.growth_entry.set_precision(self.decimals)
         self.growth_entry.setSingleStep(0.01)
 
         self.growth_label.setToolTip(_("How thick the copper growth is intended to be.\n"
                                        "In microns."))
-        grid0.addWidget(self.growth_label, 7, 0)
-        grid0.addWidget(self.growth_entry, 7, 1)
+        grid0.addWidget(self.growth_label, 14, 0)
+        grid0.addWidget(self.growth_entry, 14, 1)
 
         self.layout.addStretch()

+ 47 - 20
appGUI/preferences/tools/ToolsCornersPrefGroupUI.py

@@ -1,7 +1,7 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from appGUI.GUIElements import FCDoubleSpinner
+from appGUI.GUIElements import FCDoubleSpinner, FCLabel, RadioSet
 from appGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
@@ -32,14 +32,28 @@ class ToolsCornersPrefGroupUI(OptionsGroupUI):
         grid0.setColumnStretch(1, 1)
         self.layout.addLayout(grid0)
 
-        self.param_label = QtWidgets.QLabel('<b>%s:</b>' % _('Parameters'))
+        self.param_label = FCLabel('<b>%s:</b>' % _('Parameters'))
         self.param_label.setToolTip(
             _("Parameters used for this tool.")
         )
         grid0.addWidget(self.param_label, 0, 0, 1, 2)
 
+        # Type of Marker
+        self.type_label = FCLabel('%s:' % _("Type"))
+        self.type_label.setToolTip(
+            _("Shape of the marker.")
+        )
+
+        self.type_radio = RadioSet([
+            {"label": _("Semi-Cross"), "value": "s"},
+            {"label": _("Cross"), "value": "c"},
+        ])
+
+        grid0.addWidget(self.type_label, 2, 0)
+        grid0.addWidget(self.type_radio, 2, 1)
+        
         # Thickness #
-        self.thick_label = QtWidgets.QLabel('%s:' % _("Thickness"))
+        self.thick_label = FCLabel('%s:' % _("Thickness"))
         self.thick_label.setToolTip(
             _("The thickness of the line that makes the corner marker.")
         )
@@ -49,33 +63,46 @@ class ToolsCornersPrefGroupUI(OptionsGroupUI):
         self.thick_entry.setWrapping(True)
         self.thick_entry.setSingleStep(10 ** -self.decimals)
 
-        grid0.addWidget(self.thick_label, 1, 0)
-        grid0.addWidget(self.thick_entry, 1, 1)
+        grid0.addWidget(self.thick_label, 4, 0)
+        grid0.addWidget(self.thick_entry, 4, 1)
+
+        # Margin #
+        self.margin_label = FCLabel('%s:' % _("Margin"))
+        self.margin_label.setToolTip(
+            _("Bounding box margin.")
+        )
+        self.margin_entry = FCDoubleSpinner()
+        self.margin_entry.set_range(-10000.0000, 10000.0000)
+        self.margin_entry.set_precision(self.decimals)
+        self.margin_entry.setSingleStep(0.1)
+
+        grid0.addWidget(self.margin_label, 6, 0)
+        grid0.addWidget(self.margin_entry, 6, 1)
 
         # Length #
-        self.l_label = QtWidgets.QLabel('%s:' % _("Length"))
+        self.l_label = FCLabel('%s:' % _("Length"))
         self.l_label.setToolTip(
             _("The length of the line that makes the corner marker.")
         )
         self.l_entry = FCDoubleSpinner()
-        self.l_entry.set_range(-9999.9999, 9999.9999)
+        self.l_entry.set_range(-10000.0000, 10000.0000)
         self.l_entry.set_precision(self.decimals)
         self.l_entry.setSingleStep(10 ** -self.decimals)
 
-        # Margin #
-        self.margin_label = QtWidgets.QLabel('%s:' % _("Margin"))
-        self.margin_label.setToolTip(
-            _("Bounding box margin.")
-        )
-        self.margin_entry = FCDoubleSpinner()
-        self.margin_entry.set_range(-9999.9999, 9999.9999)
-        self.margin_entry.set_precision(self.decimals)
-        self.margin_entry.setSingleStep(0.1)
+        grid0.addWidget(self.l_label, 8, 0)
+        grid0.addWidget(self.l_entry, 8, 1)
 
-        grid0.addWidget(self.margin_label, 2, 0)
-        grid0.addWidget(self.margin_entry, 2, 1)
+        # Drill Tool Diameter
+        self.drill_dia_label = FCLabel('%s:' % _("Drill Dia"))
+        self.drill_dia_label.setToolTip(
+            '%s.' % _("Drill Diameter")
+        )
+        self.drill_dia_entry = FCDoubleSpinner()
+        self.drill_dia_entry.set_range(0.0000, 100.0000)
+        self.drill_dia_entry.set_precision(self.decimals)
+        self.drill_dia_entry.setWrapping(True)
 
-        grid0.addWidget(self.l_label, 4, 0)
-        grid0.addWidget(self.l_entry, 4, 1)
+        grid0.addWidget(self.drill_dia_label, 10, 0)
+        grid0.addWidget(self.drill_dia_entry, 10, 1)
 
         self.layout.addStretch()

+ 12 - 12
appGUI/preferences/tools/ToolsCutoutPrefGroupUI.py

@@ -47,7 +47,7 @@ class ToolsCutoutPrefGroupUI(OptionsGroupUI):
         )
 
         self.cutout_tooldia_entry = FCDoubleSpinner()
-        self.cutout_tooldia_entry.set_range(0.000001, 9999.9999)
+        self.cutout_tooldia_entry.set_range(0.000001, 10000.0000)
         self.cutout_tooldia_entry.set_precision(self.decimals)
         self.cutout_tooldia_entry.setSingleStep(0.1)
 
@@ -66,9 +66,9 @@ class ToolsCutoutPrefGroupUI(OptionsGroupUI):
         self.cutz_entry.set_precision(self.decimals)
 
         if machinist_setting == 0:
-            self.cutz_entry.setRange(-9999.9999, 0.0000)
+            self.cutz_entry.setRange(-10000.0000, 0.0000)
         else:
-            self.cutz_entry.setRange(-9999.9999, 9999.9999)
+            self.cutz_entry.setRange(-10000.0000, 10000.0000)
 
         self.cutz_entry.setSingleStep(0.1)
 
@@ -88,7 +88,7 @@ class ToolsCutoutPrefGroupUI(OptionsGroupUI):
 
         self.maxdepth_entry = FCDoubleSpinner()
         self.maxdepth_entry.set_precision(self.decimals)
-        self.maxdepth_entry.setRange(0, 9999.9999)
+        self.maxdepth_entry.setRange(0, 10000.0000)
         self.maxdepth_entry.setSingleStep(0.1)
 
         self.maxdepth_entry.setToolTip(_("Depth of each pass (positive)."))
@@ -97,11 +97,11 @@ class ToolsCutoutPrefGroupUI(OptionsGroupUI):
         grid0.addWidget(self.maxdepth_entry, 2, 1)
 
         # Object kind
-        kindlabel = FCLabel('%s:' % _('Object kind'))
+        kindlabel = FCLabel('%s:' % _('Kind'))
         kindlabel.setToolTip(
-            _("Choice of what kind the object we want to cutout is.<BR>"
-              "- <B>Single</B>: contain a single PCB Gerber outline object.<BR>"
-              "- <B>Panel</B>: a panel PCB Gerber object, which is made\n"
+            _("Choice of what kind the object we want to cutout is.\n"
+              "- Single: contain a single PCB Gerber outline object.\n"
+              "- Panel: a panel PCB Gerber object, which is made\n"
               "out of many individual PCB outlines.")
         )
 
@@ -120,7 +120,7 @@ class ToolsCutoutPrefGroupUI(OptionsGroupUI):
         )
 
         self.cutout_margin_entry = FCDoubleSpinner()
-        self.cutout_margin_entry.set_range(-9999.9999, 9999.9999)
+        self.cutout_margin_entry.set_range(-10000.0000, 10000.0000)
         self.cutout_margin_entry.set_precision(self.decimals)
         self.cutout_margin_entry.setSingleStep(0.1)
 
@@ -137,7 +137,7 @@ class ToolsCutoutPrefGroupUI(OptionsGroupUI):
         )
 
         self.cutout_gap_entry = FCDoubleSpinner()
-        self.cutout_gap_entry.set_range(0.000001, 9999.9999)
+        self.cutout_gap_entry.set_range(0.000001, 10000.0000)
         self.cutout_gap_entry.set_precision(self.decimals)
         self.cutout_gap_entry.setSingleStep(0.1)
 
@@ -174,9 +174,9 @@ class ToolsCutoutPrefGroupUI(OptionsGroupUI):
         self.thin_depth_entry = FCDoubleSpinner()
         self.thin_depth_entry.set_precision(self.decimals)
         if machinist_setting == 0:
-            self.thin_depth_entry.setRange(-9999.9999, -0.00001)
+            self.thin_depth_entry.setRange(-10000.0000, -0.00001)
         else:
-            self.thin_depth_entry.setRange(-9999.9999, 9999.9999)
+            self.thin_depth_entry.setRange(-10000.0000, 10000.0000)
         self.thin_depth_entry.setSingleStep(0.1)
 
         grid0.addWidget(self.thin_depth_label, 9, 0)

+ 18 - 17
appGUI/preferences/tools/ToolsDrillPrefGroupUI.py

@@ -63,9 +63,9 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
         self.cutz_entry = FCDoubleSpinner()
 
         if machinist_setting == 0:
-            self.cutz_entry.set_range(-9999.9999, 0.0000)
+            self.cutz_entry.set_range(-10000.0000, 0.0000)
         else:
-            self.cutz_entry.set_range(-9999.9999, 9999.9999)
+            self.cutz_entry.set_range(-10000.0000, 10000.0000)
 
         self.cutz_entry.setSingleStep(0.1)
         self.cutz_entry.set_precision(self.decimals)
@@ -86,7 +86,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
 
         self.maxdepth_entry = FCDoubleSpinner()
         self.maxdepth_entry.set_precision(self.decimals)
-        self.maxdepth_entry.set_range(0, 9999.9999)
+        self.maxdepth_entry.set_range(0, 10000.0000)
         self.maxdepth_entry.setSingleStep(0.1)
 
         self.maxdepth_entry.setToolTip(_("Depth of each pass (positive)."))
@@ -105,9 +105,9 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
         self.travelz_entry.set_precision(self.decimals)
 
         if machinist_setting == 0:
-            self.travelz_entry.set_range(0.0001, 9999.9999)
+            self.travelz_entry.set_range(0.0001, 10000.0000)
         else:
-            self.travelz_entry.set_range(-9999.9999, 9999.9999)
+            self.travelz_entry.set_range(-10000.0000, 10000.0000)
 
         grid0.addWidget(travelzlabel, 5, 0)
         grid0.addWidget(self.travelz_entry, 5, 1, 1, 2)
@@ -131,9 +131,9 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
         self.toolchangez_entry.set_precision(self.decimals)
 
         if machinist_setting == 0:
-            self.toolchangez_entry.set_range(0.0001, 9999.9999)
+            self.toolchangez_entry.set_range(0.0001, 10000.0000)
         else:
-            self.toolchangez_entry.set_range(-9999.9999, 9999.9999)
+            self.toolchangez_entry.set_range(-10000.0000, 10000.0000)
 
         grid0.addWidget(toolchangezlabel, 7, 0)
         grid0.addWidget(self.toolchangez_entry, 7, 1, 1, 2)
@@ -148,9 +148,9 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
         self.endz_entry.set_precision(self.decimals)
 
         if machinist_setting == 0:
-            self.endz_entry.set_range(0.0000, 9999.9999)
+            self.endz_entry.set_range(0.0000, 10000.0000)
         else:
-            self.endz_entry.set_range(-9999.9999, 9999.9999)
+            self.endz_entry.set_range(-10000.0000, 10000.0000)
 
         grid0.addWidget(endz_label, 8, 0)
         grid0.addWidget(self.endz_entry, 8, 1, 1, 2)
@@ -177,7 +177,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
         )
         self.feedrate_z_entry = FCDoubleSpinner()
         self.feedrate_z_entry.set_precision(self.decimals)
-        self.feedrate_z_entry.set_range(0, 99999.9999)
+        self.feedrate_z_entry.set_range(0, 910000.0000)
 
         grid0.addWidget(frlabel, 10, 0)
         grid0.addWidget(self.feedrate_z_entry, 10, 1, 1, 2)
@@ -210,7 +210,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
         dwelltime.setToolTip(_("Number of time units for spindle to dwell."))
         self.dwelltime_entry = FCDoubleSpinner()
         self.dwelltime_entry.set_precision(self.decimals)
-        self.dwelltime_entry.set_range(0, 99999.9999)
+        self.dwelltime_entry.set_range(0, 910000.0000)
 
         grid0.addWidget(dwelltime, 13, 0)
         grid0.addWidget(self.dwelltime_entry, 13, 1, 1, 2)
@@ -226,6 +226,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
 
         self.pp_excellon_name_cb = FCComboBox()
         self.pp_excellon_name_cb.setFocusPolicy(Qt.StrongFocus)
+        self.pp_excellon_name_cb.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
 
         grid0.addWidget(pp_excellon_label, 14, 0)
         grid0.addWidget(self.pp_excellon_name_cb, 14, 1, 1, 2)
@@ -254,7 +255,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
 
         self.drill_overlap_entry = FCDoubleSpinner()
         self.drill_overlap_entry.set_precision(self.decimals)
-        self.drill_overlap_entry.set_range(0.0, 9999.9999)
+        self.drill_overlap_entry.set_range(0.0, 10000.0000)
         self.drill_overlap_entry.setSingleStep(0.1)
 
         grid0.addWidget(self.drill_overlap_label, 22, 0)
@@ -305,7 +306,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
         # Start Z
         startzlabel = FCLabel('%s:' % _('Start Z'))
         startzlabel.setToolTip(
-            _("Height of the tool just after start.\n"
+            _("Height of the tool just after starting the work.\n"
               "Delete the value if you don't need this feature.")
         )
         self.estartz_entry = NumericalEvalEntry(border_color='#0069A9')
@@ -324,7 +325,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
         )
         self.feedrate_rapid_entry = FCDoubleSpinner()
         self.feedrate_rapid_entry.set_precision(self.decimals)
-        self.feedrate_rapid_entry.set_range(0, 99999.9999)
+        self.feedrate_rapid_entry.set_range(0, 910000.0000)
 
         grid0.addWidget(fr_rapid_label, 35, 0)
         grid0.addWidget(self.feedrate_rapid_entry, 35, 1, 1, 2)
@@ -337,7 +338,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
         )
         self.pdepth_entry = FCDoubleSpinner()
         self.pdepth_entry.set_precision(self.decimals)
-        self.pdepth_entry.set_range(-99999.9999, 0.0000)
+        self.pdepth_entry.set_range(-910000.0000, 0.0000)
 
         grid0.addWidget(self.pdepth_label, 37, 0)
         grid0.addWidget(self.pdepth_entry, 37, 1, 1, 2)
@@ -349,7 +350,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
         )
         self.feedrate_probe_entry = FCDoubleSpinner()
         self.feedrate_probe_entry.set_precision(self.decimals)
-        self.feedrate_probe_entry.set_range(0, 99999.9999)
+        self.feedrate_probe_entry.set_range(0, 910000.0000)
 
         grid0.addWidget(self.feedrate_probe_label, 38, 0)
         grid0.addWidget(self.feedrate_probe_entry, 38, 1, 1, 2)
@@ -443,7 +444,7 @@ class ToolsDrillPrefGroupUI(OptionsGroupUI):
         self.over_z_label.setToolTip(_("The height Z to which the tool will rise in order to avoid\n"
                                        "an interdiction area."))
         self.over_z_entry = FCDoubleSpinner()
-        self.over_z_entry.set_range(0.000, 9999.9999)
+        self.over_z_entry.set_range(0.000, 10000.0000)
         self.over_z_entry.set_precision(self.decimals)
 
         grid0.addWidget(self.over_z_label, 55, 0)

+ 4 - 4
appGUI/preferences/tools/ToolsFilmPrefGroupUI.py

@@ -65,7 +65,7 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
         # Film Border
         self.film_boundary_entry = FCDoubleSpinner()
         self.film_boundary_entry.set_precision(self.decimals)
-        self.film_boundary_entry.set_range(0, 9999.9999)
+        self.film_boundary_entry.set_range(0, 10000.0000)
         self.film_boundary_entry.setSingleStep(0.1)
 
         self.film_boundary_label = FCLabel('%s:' % _("Border"))
@@ -84,7 +84,7 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
 
         self.film_scale_stroke_entry = FCDoubleSpinner()
         self.film_scale_stroke_entry.set_precision(self.decimals)
-        self.film_scale_stroke_entry.set_range(0, 9999.9999)
+        self.film_scale_stroke_entry.set_range(0, 10000.0000)
         self.film_scale_stroke_entry.setSingleStep(0.1)
 
         self.film_scale_stroke_label = FCLabel('%s:' % _("Scale Stroke"))
@@ -198,7 +198,7 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
                                           {'label': _('Y'), 'value': 'y'},
                                           {'label': _('Both'), 'value': 'both'}],
                                          stretch=False)
-        self.film_mirror_axis_label = FCLabel('%s:' % _("Mirror axis"))
+        self.film_mirror_axis_label = FCLabel('%s:' % _("Mirror Axis"))
 
         grid0.addWidget(self.film_mirror_axis_label, 13, 0)
         grid0.addWidget(self.film_mirror_axis, 13, 1)
@@ -213,7 +213,7 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
                                          {'label': _('PDF'), 'value': 'pdf'}
                                          ], stretch=False)
 
-        self.file_type_label = FCLabel(_("Film Type:"))
+        self.file_type_label = FCLabel('%s:' % _("Film Type"))
         self.file_type_label.setToolTip(
             _("The file type of the saved film. Can be:\n"
               "- 'SVG' -> open-source vectorial format\n"

+ 13 - 13
appGUI/preferences/tools/ToolsISOPrefGroupUI.py

@@ -1,7 +1,7 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from appGUI.GUIElements import RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, NumericalEvalTupleEntry
+from appGUI.GUIElements import RadioSet, FCDoubleSpinner, FCComboBox2, FCCheckBox, FCSpinner, NumericalEvalTupleEntry
 from appGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
@@ -115,16 +115,16 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
         cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
         cutzlabel.setToolTip(
            _("Depth of cut into material. Negative value.\n"
-             "In FlatCAM units.")
+             "In application units.")
         )
         self.cutz_entry = FCDoubleSpinner()
         self.cutz_entry.set_precision(self.decimals)
-        self.cutz_entry.set_range(-9999.9999, 0.0000)
+        self.cutz_entry.set_range(-10000.0000, 0.0000)
         self.cutz_entry.setSingleStep(0.1)
 
         self.cutz_entry.setToolTip(
            _("Depth of cut into material. Negative value.\n"
-             "In FlatCAM units.")
+             "In application units.")
         )
 
         grid0.addWidget(cutzlabel, 5, 0)
@@ -139,7 +139,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
         )
         self.newdia_entry = FCDoubleSpinner()
         self.newdia_entry.set_precision(self.decimals)
-        self.newdia_entry.set_range(0.0001, 9999.9999)
+        self.newdia_entry.set_range(0.0001, 10000.0000)
         self.newdia_entry.setSingleStep(0.1)
 
         grid0.addWidget(self.newdialabel, 6, 0)
@@ -181,7 +181,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
         # Milling Type Radio Button
         self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
         self.milling_type_label.setToolTip(
-            _("Milling type when the selected tool is of type: 'iso_op':\n"
+            _("Milling type:\n"
               "- climb / best for precision milling and to reduce tool usage\n"
               "- conventional / useful when there is no backlash compensation")
         )
@@ -189,7 +189,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
         self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
                                             {'label': _('Conventional'), 'value': 'cv'}])
         self.milling_type_radio.setToolTip(
-            _("Milling type when the selected tool is of type: 'iso_op':\n"
+            _("Milling type:\n"
               "- climb / best for precision milling and to reduce tool usage\n"
               "- conventional / useful when there is no backlash compensation")
         )
@@ -244,11 +244,11 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
         self.rest_cb.setObjectName("i_rest_machining")
         self.rest_cb.setToolTip(
             _("If checked, use 'rest machining'.\n"
-              "Basically it will isolate outside PCB features,\n"
+              "Basically it will process copper outside PCB features,\n"
               "using the biggest tool and continue with the next tools,\n"
-              "from bigger to smaller, to isolate the copper features that\n"
-              "could not be cleared by previous tool, until there is\n"
-              "no more copper features to isolate or there are no more tools.\n"
+              "from bigger to smaller, to process the copper features that\n"
+              "could not be processed by previous tool, until there is\n"
+              "nothing left to process or there are no more tools.\n\n"
               "If not checked, use the standard algorithm.")
         )
 
@@ -290,7 +290,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
               "- 'Polygon Selection' -> Isolate a selection of polygons.\n"
               "- 'Reference Object' - will process the area specified by another object.")
         )
-        self.select_combo = FCComboBox()
+        self.select_combo = FCComboBox2()
         self.select_combo.addItems(
             [_("All"), _("Area Selection"), _("Polygon Selection"), _("Reference Object")]
         )
@@ -338,7 +338,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
                                         {"label": _("Progressive"), "value": "progressive"}])
         plotting_label = QtWidgets.QLabel('%s:' % _("Plotting"))
         plotting_label.setToolTip(
-            _("- 'Normal' -  normal plotting, done at the end of the job\n"
+            _("- 'Normal' - normal plotting, done at the end of the job\n"
               "- 'Progressive' - each shape is plotted after it is generated")
         )
         grid0.addWidget(plotting_label, 25, 0)

+ 19 - 21
appGUI/preferences/tools/ToolsNCCPrefGroupUI.py

@@ -1,7 +1,7 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from appGUI.GUIElements import RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox, NumericalEvalTupleEntry
+from appGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, NumericalEvalTupleEntry, FCComboBox2
 from appGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
@@ -98,16 +98,16 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
         cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
         cutzlabel.setToolTip(
            _("Depth of cut into material. Negative value.\n"
-             "In FlatCAM units.")
+             "In application units.")
         )
         self.cutz_entry = FCDoubleSpinner()
         self.cutz_entry.set_precision(self.decimals)
-        self.cutz_entry.set_range(-9999.9999, 0.0000)
+        self.cutz_entry.set_range(-10000.0000, 0.0000)
         self.cutz_entry.setSingleStep(0.1)
 
         self.cutz_entry.setToolTip(
            _("Depth of cut into material. Negative value.\n"
-             "In FlatCAM units.")
+             "In application units.")
         )
 
         grid0.addWidget(cutzlabel, 4, 0)
@@ -122,7 +122,7 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
         )
         self.newdia_entry = FCDoubleSpinner()
         self.newdia_entry.set_precision(self.decimals)
-        self.newdia_entry.set_range(0.0001, 9999.9999)
+        self.newdia_entry.set_range(0.0001, 10000.0000)
         self.newdia_entry.setSingleStep(0.1)
 
         grid0.addWidget(self.newdialabel, 5, 0)
@@ -136,7 +136,7 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
         # Milling Type Radio Button
         self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
         self.milling_type_label.setToolTip(
-            _("Milling type when the selected tool is of type: 'iso_op':\n"
+            _("Milling type:\n"
               "- climb / best for precision milling and to reduce tool usage\n"
               "- conventional / useful when there is no backlash compensation")
         )
@@ -144,7 +144,7 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
         self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
                                             {'label': _('Conventional'), 'value': 'cv'}])
         self.milling_type_radio.setToolTip(
-            _("Milling type when the selected tool is of type: 'iso_op':\n"
+            _("Milling type:\n"
               "- climb / best for precision milling and to reduce tool usage\n"
               "- conventional / useful when there is no backlash compensation")
         )
@@ -183,8 +183,8 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
         nccoverlabel.setToolTip(
            _("How much (percentage) of the tool width to overlap each tool pass.\n"
              "Adjust the value starting with lower values\n"
-             "and increasing it if areas that should be cleared are still \n"
-             "not cleared.\n"
+             "and increasing it if areas that should be processed are still \n"
+             "not processed.\n"
              "Lower values = faster processing, faster execution on CNC.\n"
              "Higher values = slow processing and slow execution on CNC\n"
              "due of too many paths.")
@@ -225,7 +225,7 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
         #     {"label": _("Seed-based"), "value": "seed"},
         #     {"label": _("Straight lines"), "value": "lines"}
         # ], orientation='vertical', stretch=False)
-        self.ncc_method_combo = FCComboBox()
+        self.ncc_method_combo = FCComboBox2()
         self.ncc_method_combo.addItems(
             [_("Standard"), _("Seed"), _("Lines"), _("Combo")]
         )
@@ -256,8 +256,7 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
         self.ncc_choice_offset_cb.setToolTip(
             _("If used, it will add an offset to the copper features.\n"
               "The copper clearing will finish to a distance\n"
-              "from the copper features.\n"
-              "The value can be between 0 and 10 FlatCAM units.")
+              "from the copper features.")
         )
 
         grid0.addWidget(self.ncc_choice_offset_cb, 14, 0, 1, 2)
@@ -267,11 +266,10 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
         self.ncc_offset_label.setToolTip(
             _("If used, it will add an offset to the copper features.\n"
               "The copper clearing will finish to a distance\n"
-              "from the copper features.\n"
-              "The value can be between 0.0 and 9999.9 FlatCAM units.")
+              "from the copper features.")
         )
         self.ncc_offset_spinner = FCDoubleSpinner()
-        self.ncc_offset_spinner.set_range(0.00, 9999.9999)
+        self.ncc_offset_spinner.set_range(0.00, 10000.0000)
         self.ncc_offset_spinner.set_precision(self.decimals)
         self.ncc_offset_spinner.setWrapping(True)
         self.ncc_offset_spinner.setSingleStep(0.1)
@@ -288,11 +286,11 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
         self.ncc_rest_cb = FCCheckBox('%s' % _("Rest"))
         self.ncc_rest_cb.setToolTip(
             _("If checked, use 'rest machining'.\n"
-              "Basically it will clear copper outside PCB features,\n"
+              "Basically it will process copper outside PCB features,\n"
               "using the biggest tool and continue with the next tools,\n"
-              "from bigger to smaller, to clear areas of copper that\n"
-              "could not be cleared by previous tool, until there is\n"
-              "no more copper to clear or there are no more tools.\n"
+              "from bigger to smaller, to process the copper features that\n"
+              "could not be processed by previous tool, until there is\n"
+              "nothing left to process or there are no more tools.\n\n"
               "If not checked, use the standard algorithm.")
         )
 
@@ -304,7 +302,7 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
         #                                  {'label': _('Reference Object'), 'value': 'box'}],
         #                                 orientation='vertical',
         #                                 stretch=None)
-        self.select_combo = FCComboBox()
+        self.select_combo = FCComboBox2()
         self.select_combo.addItems(
             [_("Itself"), _("Area Selection"), _("Reference Object")]
         )
@@ -340,7 +338,7 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
                                         {"label": _("Progressive"), "value": "progressive"}])
         plotting_label = QtWidgets.QLabel('%s:' % _("Plotting"))
         plotting_label.setToolTip(
-            _("- 'Normal' -  normal plotting, done at the end of the job\n"
+            _("- 'Normal' - normal plotting, done at the end of the job\n"
               "- 'Progressive' - each shape is plotted after it is generated")
         )
         grid0.addWidget(plotting_label, 21, 0)

+ 16 - 16
appGUI/preferences/tools/ToolsPaintPrefGroupUI.py

@@ -1,7 +1,7 @@
 from PyQt5 import QtWidgets
 from PyQt5.QtCore import QSettings
 
-from appGUI.GUIElements import RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox, NumericalEvalTupleEntry
+from appGUI.GUIElements import RadioSet, FCDoubleSpinner, FCComboBox2, FCCheckBox, NumericalEvalTupleEntry
 from appGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
 import gettext
@@ -30,7 +30,7 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
         # ------------------------------
         # ## Paint area
         # ------------------------------
-        self.paint_label = QtWidgets.QLabel(_('<b>Parameters:</b>'))
+        self.paint_label = QtWidgets.QLabel('<b>%s:</b>' % _('Parameters'))
         self.paint_label.setToolTip(
             _("Creates tool paths to cover the\n"
               "whole area of a polygon.")
@@ -78,7 +78,7 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
             _("The tip diameter for V-Shape Tool"))
         self.tipdia_entry = FCDoubleSpinner()
         self.tipdia_entry.set_precision(self.decimals)
-        self.tipdia_entry.set_range(0.0000, 9999.9999)
+        self.tipdia_entry.set_range(0.0000, 10000.0000)
         self.tipdia_entry.setSingleStep(0.1)
         self.tipdia_entry.setObjectName(_("V-Tip Dia"))
 
@@ -107,7 +107,7 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
         )
         self.cutz_entry = FCDoubleSpinner()
         self.cutz_entry.set_precision(self.decimals)
-        self.cutz_entry.set_range(-99999.9999, 0.0000)
+        self.cutz_entry.set_range(-910000.0000, 0.0000)
         self.cutz_entry.setObjectName(_("Cut Z"))
 
         self.cutz_entry.setToolTip(
@@ -126,7 +126,7 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
         )
         self.newdia_entry = FCDoubleSpinner()
         self.newdia_entry.set_precision(self.decimals)
-        self.newdia_entry.set_range(0.000, 9999.9999)
+        self.newdia_entry.set_range(0.000, 10000.0000)
         self.newdia_entry.setObjectName(_("Tool Dia"))
 
         grid0.addWidget(self.newdialabel, 5, 0)
@@ -162,8 +162,8 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
         ovlabel.setToolTip(
             _("How much (percentage) of the tool width to overlap each tool pass.\n"
               "Adjust the value starting with lower values\n"
-              "and increasing it if areas that should be painted are still \n"
-              "not painted.\n"
+              "and increasing it if areas that should be processed are still \n"
+              "not processed.\n"
               "Lower values = faster processing, faster execution on CNC.\n"
               "Higher values = slow processing and slow execution on CNC\n"
               "due of too many paths.")
@@ -185,7 +185,7 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
               "be painted.")
         )
         self.paintmargin_entry = FCDoubleSpinner()
-        self.paintmargin_entry.set_range(-9999.9999, 9999.9999)
+        self.paintmargin_entry.set_range(-10000.0000, 10000.0000)
         self.paintmargin_entry.set_precision(self.decimals)
         self.paintmargin_entry.setSingleStep(0.1)
 
@@ -210,7 +210,7 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
         #     {"label": _("Seed-based"), "value": "seed"},
         #     {"label": _("Straight lines"), "value": "lines"}
         # ], orientation='vertical', stretch=False)
-        self.paintmethod_combo = FCComboBox()
+        self.paintmethod_combo = FCComboBox2()
         self.paintmethod_combo.addItems(
             [_("Standard"), _("Seed"), _("Lines"), _("Laser_lines"), _("Combo")]
         )
@@ -243,11 +243,11 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
         self.rest_cb.setObjectName(_("Rest"))
         self.rest_cb.setToolTip(
             _("If checked, use 'rest machining'.\n"
-              "Basically it will clear copper outside PCB features,\n"
+              "Basically it will process copper outside PCB features,\n"
               "using the biggest tool and continue with the next tools,\n"
-              "from bigger to smaller, to clear areas of copper that\n"
-              "could not be cleared by previous tool, until there is\n"
-              "no more copper to clear or there are no more tools.\n\n"
+              "from bigger to smaller, to process the copper features that\n"
+              "could not be processed by previous tool, until there is\n"
+              "nothing left to process or there are no more tools.\n\n"
               "If not checked, use the standard algorithm.")
         )
         grid0.addWidget(self.rest_cb, 14, 0, 1, 2)
@@ -273,9 +273,9 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
         #     orientation='vertical',
         #     stretch=None
         # )
-        self.selectmethod_combo = FCComboBox()
+        self.selectmethod_combo = FCComboBox2()
         self.selectmethod_combo.addItems(
-            [_("Polygon Selection"), _("Area Selection"), _("All"), _("Reference Object")]
+            [_("All"), _("Polygon Selection"), _("Area Selection"), _("Reference Object")]
         )
 
         grid0.addWidget(selectlabel, 15, 0)
@@ -302,7 +302,7 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
                                               {"label": _("Progressive"), "value": "progressive"}])
         plotting_label = QtWidgets.QLabel('%s:' % _("Plotting"))
         plotting_label.setToolTip(
-            _("- 'Normal' -  normal plotting, done at the end of the job\n"
+            _("- 'Normal' - normal plotting, done at the end of the job\n"
               "- 'Progressive' - each shape is plotted after it is generated")
         )
         grid0.addWidget(plotting_label, 20, 0)

+ 4 - 4
appGUI/preferences/tools/ToolsPanelizePrefGroupUI.py

@@ -43,7 +43,7 @@ class ToolsPanelizePrefGroupUI(OptionsGroupUI):
 
         # ## Spacing Columns
         self.pspacing_columns = FCDoubleSpinner()
-        self.pspacing_columns.set_range(0.000001, 9999.9999)
+        self.pspacing_columns.set_range(0.000001, 10000.0000)
         self.pspacing_columns.set_precision(self.decimals)
         self.pspacing_columns.setSingleStep(0.1)
 
@@ -57,7 +57,7 @@ class ToolsPanelizePrefGroupUI(OptionsGroupUI):
 
         # ## Spacing Rows
         self.pspacing_rows = FCDoubleSpinner()
-        self.pspacing_rows.set_range(0.000001, 9999.9999)
+        self.pspacing_rows.set_range(0.000001, 10000.0000)
         self.pspacing_rows.set_precision(self.decimals)
         self.pspacing_rows.setSingleStep(0.1)
 
@@ -128,7 +128,7 @@ class ToolsPanelizePrefGroupUI(OptionsGroupUI):
         grid0.addWidget(self.pconstrain_cb, 10, 0, 1, 2)
 
         self.px_width_entry = FCDoubleSpinner()
-        self.px_width_entry.set_range(0.000001, 9999.9999)
+        self.px_width_entry.set_range(0.000001, 10000.0000)
         self.px_width_entry.set_precision(self.decimals)
         self.px_width_entry.setSingleStep(0.1)
 
@@ -141,7 +141,7 @@ class ToolsPanelizePrefGroupUI(OptionsGroupUI):
         grid0.addWidget(self.px_width_entry, 12, 1)
 
         self.py_height_entry = FCDoubleSpinner()
-        self.py_height_entry.set_range(0.000001, 9999.9999)
+        self.py_height_entry.set_range(0.000001, 10000.0000)
         self.py_height_entry.set_precision(self.decimals)
         self.py_height_entry.setSingleStep(0.1)
 

+ 8 - 8
appGUI/preferences/tools/ToolsPreferencesUI.py

@@ -81,26 +81,26 @@ class ToolsPreferencesUI(QtWidgets.QWidget):
         self.vlay = QtWidgets.QVBoxLayout()
 
         self.vlay.addWidget(self.tools_iso_group)
-        self.vlay.addWidget(self.tools_drill_group)
+        self.vlay.addWidget(self.tools_2sided_group)
+        self.vlay.addWidget(self.tools_cutout_group)
 
         self.vlay1 = QtWidgets.QVBoxLayout()
-        self.vlay1.addWidget(self.tools_ncc_group)
-        self.vlay1.addWidget(self.tools_2sided_group)
-        self.vlay1.addWidget(self.tools_cutout_group)
-        self.vlay1.addWidget(self.tools_sub_group)
+        self.vlay1.addWidget(self.tools_drill_group)
+        self.vlay1.addWidget(self.tools_panelize_group)
 
         self.vlay2 = QtWidgets.QVBoxLayout()
+        self.vlay2.addWidget(self.tools_ncc_group)
         self.vlay2.addWidget(self.tools_paint_group)
-        self.vlay2.addWidget(self.tools_transform_group)
 
         self.vlay3 = QtWidgets.QVBoxLayout()
         self.vlay3.addWidget(self.tools_film_group)
-        self.vlay3.addWidget(self.tools_calculators_group)
+        self.vlay3.addWidget(self.tools_transform_group)
 
         self.vlay4 = QtWidgets.QVBoxLayout()
         self.vlay4.addWidget(self.tools_solderpaste_group)
         self.vlay4.addWidget(self.tools_corners_group)
-        self.vlay4.addWidget(self.tools_panelize_group)
+        self.vlay4.addWidget(self.tools_calculators_group)
+        self.vlay4.addWidget(self.tools_sub_group)
 
         self.layout.addLayout(self.vlay)
         self.layout.addLayout(self.vlay1)

+ 12 - 12
appGUI/preferences/tools/ToolsSolderpastePrefGroupUI.py

@@ -53,11 +53,11 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
         # New Nozzle Tool Dia
         self.addtool_entry_lbl = QtWidgets.QLabel('<b>%s:</b>' % _('New Nozzle Dia'))
         self.addtool_entry_lbl.setToolTip(
-            _("Diameter for the new Nozzle tool to add in the Tool Table")
+            _("Diameter for the new tool to add in the Tool Table")
         )
         self.addtool_entry = FCDoubleSpinner()
         self.addtool_entry.set_precision(self.decimals)
-        self.addtool_entry.set_range(0.0000001, 9999.9999)
+        self.addtool_entry.set_range(0.0000001, 10000.0000)
         self.addtool_entry.setSingleStep(0.1)
 
         grid0.addWidget(self.addtool_entry_lbl, 1, 0)
@@ -66,7 +66,7 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
         # Z dispense start
         self.z_start_entry = FCDoubleSpinner()
         self.z_start_entry.set_precision(self.decimals)
-        self.z_start_entry.set_range(0.0000001, 9999.9999)
+        self.z_start_entry.set_range(0.0000001, 10000.0000)
         self.z_start_entry.setSingleStep(0.1)
 
         self.z_start_label = QtWidgets.QLabel('%s:' % _("Z Dispense Start"))
@@ -79,7 +79,7 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
         # Z dispense
         self.z_dispense_entry = FCDoubleSpinner()
         self.z_dispense_entry.set_precision(self.decimals)
-        self.z_dispense_entry.set_range(0.0000001, 9999.9999)
+        self.z_dispense_entry.set_range(0.0000001, 10000.0000)
         self.z_dispense_entry.setSingleStep(0.1)
 
         self.z_dispense_label = QtWidgets.QLabel('%s:' % _("Z Dispense"))
@@ -92,7 +92,7 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
         # Z dispense stop
         self.z_stop_entry = FCDoubleSpinner()
         self.z_stop_entry.set_precision(self.decimals)
-        self.z_stop_entry.set_range(0.0000001, 9999.9999)
+        self.z_stop_entry.set_range(0.0000001, 10000.0000)
         self.z_stop_entry.setSingleStep(0.1)
 
         self.z_stop_label = QtWidgets.QLabel('%s:' % _("Z Dispense Stop"))
@@ -105,7 +105,7 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
         # Z travel
         self.z_travel_entry = FCDoubleSpinner()
         self.z_travel_entry.set_precision(self.decimals)
-        self.z_travel_entry.set_range(0.0000001, 9999.9999)
+        self.z_travel_entry.set_range(0.0000001, 10000.0000)
         self.z_travel_entry.setSingleStep(0.1)
 
         self.z_travel_label = QtWidgets.QLabel('%s:' % _("Z Travel"))
@@ -119,7 +119,7 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
         # Z toolchange location
         self.z_toolchange_entry = FCDoubleSpinner()
         self.z_toolchange_entry.set_precision(self.decimals)
-        self.z_toolchange_entry.set_range(0.0000001, 9999.9999)
+        self.z_toolchange_entry.set_range(0.0000001, 10000.0000)
         self.z_toolchange_entry.setSingleStep(0.1)
 
         self.z_toolchange_label = QtWidgets.QLabel('%s:' % _("Z Toolchange"))
@@ -142,7 +142,7 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
         # Feedrate X-Y
         self.frxy_entry = FCDoubleSpinner()
         self.frxy_entry.set_precision(self.decimals)
-        self.frxy_entry.set_range(0.0000001, 99999.9999)
+        self.frxy_entry.set_range(0.0000001, 910000.0000)
         self.frxy_entry.setSingleStep(0.1)
 
         self.frxy_label = QtWidgets.QLabel('%s:' % _("Feedrate X-Y"))
@@ -155,7 +155,7 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
         # Feedrate Z
         self.frz_entry = FCDoubleSpinner()
         self.frz_entry.set_precision(self.decimals)
-        self.frz_entry.set_range(0.0000001, 99999.9999)
+        self.frz_entry.set_range(0.0000001, 910000.0000)
         self.frz_entry.setSingleStep(0.1)
 
         self.frz_label = QtWidgets.QLabel('%s:' % _("Feedrate Z"))
@@ -169,7 +169,7 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
         # Feedrate Z Dispense
         self.frz_dispense_entry = FCDoubleSpinner()
         self.frz_dispense_entry.set_precision(self.decimals)
-        self.frz_dispense_entry.set_range(0.0000001, 99999.9999)
+        self.frz_dispense_entry.set_range(0.0000001, 910000.0000)
         self.frz_dispense_entry.setSingleStep(0.1)
 
         self.frz_dispense_label = QtWidgets.QLabel('%s:' % _("Feedrate Z Dispense"))
@@ -196,7 +196,7 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
         # Dwell Forward
         self.dwellfwd_entry = FCDoubleSpinner()
         self.dwellfwd_entry.set_precision(self.decimals)
-        self.dwellfwd_entry.set_range(0.0000001, 9999.9999)
+        self.dwellfwd_entry.set_range(0.0000001, 10000.0000)
         self.dwellfwd_entry.setSingleStep(0.1)
 
         self.dwellfwd_label = QtWidgets.QLabel('%s:' % _("Dwell FWD"))
@@ -222,7 +222,7 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
         # Dwell Reverse
         self.dwellrev_entry = FCDoubleSpinner()
         self.dwellrev_entry.set_precision(self.decimals)
-        self.dwellrev_entry.set_range(0.0000001, 9999.9999)
+        self.dwellrev_entry.set_range(0.0000001, 10000.0000)
         self.dwellrev_entry.setSingleStep(0.1)
 
         self.dwellrev_label = QtWidgets.QLabel('%s:' % _("Dwell REV"))

+ 7 - 1
appGUI/preferences/tools/ToolsSubPrefGroupUI.py

@@ -36,7 +36,13 @@ class ToolsSubPrefGroupUI(OptionsGroupUI):
         self.layout.addWidget(self.sublabel)
 
         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 subtractor object."))
         self.layout.addWidget(self.close_paths_cb)
 
+        self.delete_sources_cb = FCCheckBox(_("Delete source"))
+        self.delete_sources_cb.setToolTip(
+            _("When checked will delete the source objects\n"
+              "after a successful operation.")
+        )
+        self.layout.addWidget(self.delete_sources_cb)
         self.layout.addStretch()

+ 8 - 8
appGUI/preferences/tools/ToolsTransformPrefGroupUI.py

@@ -95,7 +95,7 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
 
         self.rotate_label = QtWidgets.QLabel('%s:' % _("Angle"))
         self.rotate_label.setToolTip(
-            _("Angle for Rotation action, in degrees.\n"
+            _("Angle, in degrees.\n"
               "Float number between -360 and 359.\n"
               "Positive numbers for CW motion.\n"
               "Negative numbers for CCW motion.")
@@ -123,7 +123,7 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
 
         self.skewx_label = QtWidgets.QLabel('%s:' % _("X angle"))
         self.skewx_label.setToolTip(
-            _("Angle for Skew action, in degrees.\n"
+            _("Angle, in degrees.\n"
               "Float number between -360 and 359.")
         )
         grid0.addWidget(self.skewx_label, 9, 0)
@@ -137,7 +137,7 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
 
         self.skewy_label = QtWidgets.QLabel('%s:' % _("Y angle"))
         self.skewy_label.setToolTip(
-            _("Angle for Skew action, in degrees.\n"
+            _("Angle, in degrees.\n"
               "Float number between -360 and 359.")
         )
         grid0.addWidget(self.skewy_label, 10, 0)
@@ -155,7 +155,7 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
         grid0.addWidget(self.scale_link_cb, 12, 1)
 
         self.scalex_entry = FCDoubleSpinner()
-        self.scalex_entry.set_range(0, 9999.9999)
+        self.scalex_entry.set_range(0, 10000.0000)
         self.scalex_entry.set_precision(self.decimals)
         self.scalex_entry.setSingleStep(0.1)
 
@@ -168,7 +168,7 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
 
         # ## Scale factor on X axis
         self.scaley_entry = FCDoubleSpinner()
-        self.scaley_entry.set_range(0, 9999.9999)
+        self.scaley_entry.set_range(0, 10000.0000)
         self.scaley_entry.set_precision(self.decimals)
         self.scaley_entry.setSingleStep(0.1)
 
@@ -184,7 +184,7 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
         grid0.addWidget(offset_title_lbl, 20, 0, 1, 2)
 
         self.offx_entry = FCDoubleSpinner()
-        self.offx_entry.set_range(-9999.9999, 9999.9999)
+        self.offx_entry.set_range(-10000.0000, 10000.0000)
         self.offx_entry.set_precision(self.decimals)
         self.offx_entry.setSingleStep(0.1)
 
@@ -197,7 +197,7 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
 
         # ## Offset distance on Y axis
         self.offy_entry = FCDoubleSpinner()
-        self.offy_entry.set_range(-9999.9999, 9999.9999)
+        self.offy_entry.set_range(-10000.0000, 10000.0000)
         self.offy_entry.set_precision(self.decimals)
         self.offy_entry.setSingleStep(0.1)
 
@@ -235,7 +235,7 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
         self.buffer_entry.set_precision(self.decimals)
         self.buffer_entry.setSingleStep(0.1)
         self.buffer_entry.setWrapping(True)
-        self.buffer_entry.set_range(-9999.9999, 9999.9999)
+        self.buffer_entry.set_range(-10000.0000, 10000.0000)
 
         grid0.addWidget(self.buffer_label, 28, 0)
         grid0.addWidget(self.buffer_entry, 28, 1)

+ 4 - 5
appObjects/AppObject.py

@@ -168,7 +168,7 @@ class AppObject(QtCore.QObject):
             return "fail"
 
         t2 = time.time()
-        msg = "New object with name: %s. %f seconds executing initialize()." % (name, (t2 - t1))
+        msg = "%s %s. %f seconds executing initialize()." % (_("New object with name:"), name, (t2 - t1))
         log.debug(msg)
         self.app.inform_shell.emit(msg)
 
@@ -256,7 +256,7 @@ class AppObject(QtCore.QObject):
                     'tooldia': float(app.defaults["geometry_cnctooldia"]),
                     'offset': 'Path',
                     'offset_value': 0.0,
-                    'type': _('Rough'),
+                    'type': 'Rough',
                     'tool_type': 'C1',
                     'data': deepcopy(default_data),
                     'solid_geometry': []
@@ -401,7 +401,6 @@ class AppObject(QtCore.QObject):
                 name=str(obj.options['name']), tx=_("created/selected"))
             )
 
-
         # ############################################################################################################
         # Set the colors for the objects that have geometry
         # ############################################################################################################
@@ -439,7 +438,7 @@ class AppObject(QtCore.QObject):
         # #############################################################################################################
         # update the SHELL auto-completer model with the name of the new object
         # #############################################################################################################
-        self.app.shell._edit.set_model_data(self.app.myKeywords)
+        self.app.shell.command_line().set_model_data(self.app.myKeywords)
 
         if auto_select or self.app.ui.notebook.currentWidget() is self.app.ui.properties_tab:
             # select the just opened object but deselect the previous ones
@@ -450,7 +449,7 @@ class AppObject(QtCore.QObject):
 
         # here it is done the object plotting
         def plotting_task(t_obj):
-            with self.app.proc_container.new(_("Plotting")):
+            with self.app.proc_container.new('%s ...' % _("Plotting")):
                 if t_obj.kind == 'cncjob':
                     t_obj.plot(kind=self.app.defaults["cncjob_plot_kind"])
                 if t_obj.kind == 'gerber':

+ 86 - 59
appObjects/FlatCAMCNCJob.py

@@ -188,8 +188,8 @@ class CNCJobObject(FlatCAMObj, CNCjob):
         self.mm = None
         self.mr = None
 
-        self.append_snippet = ''
         self.prepend_snippet = ''
+        self.append_snippet = ''
         self.gc_header = self.gcode_header()
         self.gc_start = ''
         self.gc_end = ''
@@ -372,7 +372,12 @@ class CNCJobObject(FlatCAMObj, CNCjob):
             dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, float(tooldia_key)))
             nr_drills_item = QtWidgets.QTableWidgetItem('%d' % int(dia_value['nr_drills']))
             nr_slots_item = QtWidgets.QTableWidgetItem('%d' % int(dia_value['nr_slots']))
-            cutz_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, float(dia_value['offset']) + self.z_cut))
+            try:
+                offset_val = self.app.dec_format(float(dia_value['offset']), self.decimals) + self.z_cut
+            except KeyError:
+                offset_val = self.app.dec_format(float(dia_value['offset_z']), self.decimals) + self.z_cut
+
+            cutz_item = QtWidgets.QTableWidgetItem('%f' % offset_val)
 
             t_id.setFlags(QtCore.Qt.ItemIsEnabled)
             dia_item.setFlags(QtCore.Qt.ItemIsEnabled)
@@ -527,7 +532,7 @@ class CNCJobObject(FlatCAMObj, CNCjob):
         self.append_snippet = self.app.defaults['cncjob_append']
         self.prepend_snippet = self.app.defaults['cncjob_prepend']
 
-        if self.append_snippet != '' or self.prepend_snippet:
+        if self.append_snippet != '' or self.prepend_snippet != '':
             self.ui.snippets_cb.set_value(True)
 
         # Fill form fields only on object create
@@ -638,16 +643,13 @@ class CNCJobObject(FlatCAMObj, CNCjob):
 
         # Show/Hide Advanced Options
         if self.app.defaults["global_app_level"] == 'b':
-            self.ui.level.setText(_(
-                '<span style="color:green;"><b>Basic</b></span>'
-            ))
+            self.ui.level.setText('<span style="color:green;"><b>%s</b></span>' % _("Basic"))
 
             self.ui.sal_btn.hide()
             self.ui.sal_btn.setChecked(False)
         else:
-            self.ui.level.setText(_(
-                '<span style="color:red;"><b>Advanced</b></span>'
-            ))
+            self.ui.level.setText('<span style="color:red;"><b>%s</b></span>' % _("Advanced"))
+
             if 'Roland' in self.pp_excellon_name or 'Roland' in self.pp_geometry_name or 'hpgl' in \
                     self.pp_geometry_name:
                 self.ui.sal_btn.hide()
@@ -656,8 +658,8 @@ class CNCJobObject(FlatCAMObj, CNCjob):
                 self.ui.sal_btn.show()
                 self.ui.sal_btn.setChecked(self.app.defaults["cncjob_al_status"])
 
-        preamble = self.append_snippet
-        postamble = self.prepend_snippet
+        preamble = self.prepend_snippet
+        postamble = self.append_snippet
         gc = self.export_gcode(preamble=preamble, postamble=postamble, to_file=True)
         self.source_file = gc.getvalue()
 
@@ -1648,7 +1650,9 @@ class CNCJobObject(FlatCAMObj, CNCjob):
                 ext_filter=_filter_
             )
         except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Code ..."), ext_filter=_filter_)
+            filename, _f = FCFileSaveDialog.get_saved_filename(
+                caption=_("Export Code ..."),
+                ext_filter=_filter_)
 
         if filename == '':
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export cancelled ..."))
@@ -1675,7 +1679,7 @@ class CNCJobObject(FlatCAMObj, CNCjob):
                 return 'fail'
 
     def on_edit_probing_gcode(self):
-        self.app.proc_container.view.set_busy(_("Loading..."))
+        self.app.proc_container.view.set_busy('%s...' % _("Loading"))
 
         gco = self.probing_gcode_text
         if gco is None or gco == '':
@@ -1838,7 +1842,9 @@ class CNCJobObject(FlatCAMObj, CNCjob):
                     ext_filter=_filter_
                 )
             except TypeError:
-                filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Code ..."), ext_filter=_filter_)
+                filename, _f = FCFileSaveDialog.get_saved_filename(
+                    caption=_("Export Code ..."),
+                    ext_filter=_filter_)
 
             if filename == '':
                 self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export cancelled ..."))
@@ -1882,7 +1888,7 @@ class CNCJobObject(FlatCAMObj, CNCjob):
         kind = self.ui.cncplot_method_combo.get_value()
 
         def worker_task():
-            with self.app.proc_container.new(_("Plotting...")):
+            with self.app.proc_container.new('%s ...' % _("Plotting")):
                 self.plot(kind=kind)
 
         self.app.worker_task.emit({'fcn': worker_task, 'params': []})
@@ -1916,7 +1922,9 @@ class CNCJobObject(FlatCAMObj, CNCjob):
                 ext_filter=_filter_
             )
         except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Code ..."), ext_filter=_filter_)
+            filename, _f = FCFileSaveDialog.get_saved_filename(
+                caption=_("Export Code ..."),
+                ext_filter=_filter_)
 
         self.export_gcode_handler(filename, is_gcode=save_gcode)
 
@@ -1939,8 +1947,8 @@ class CNCJobObject(FlatCAMObj, CNCjob):
 
         try:
             if self.ui.snippets_cb.get_value():
-                preamble = self.append_snippet
-                postamble = self.prepend_snippet
+                preamble = self.prepend_snippet
+                postamble = self.append_snippet
             gc = self.export_gcode(filename, preamble=preamble, postamble=postamble)
         except Exception as err:
             log.debug("CNCJobObject.export_gcode_handler() --> %s" % str(err))
@@ -1954,18 +1962,17 @@ class CNCJobObject(FlatCAMObj, CNCjob):
         self.app.file_saved.emit("gcode", filename)
         self.app.inform.emit('[success] %s: %s' % (_("File saved to"), filename))
 
-    def on_review_code_click(self, *args):
+    def on_review_code_click(self):
         """
         Handler activated by a button clicked when reviewing GCode.
 
-        :param args:
         :return:
         """
 
-        self.app.proc_container.view.set_busy(_("Loading..."))
+        self.app.proc_container.view.set_busy('%s...' % _("Loading"))
 
-        preamble = self.append_snippet
-        postamble = self.prepend_snippet
+        preamble = self.prepend_snippet
+        postamble = self.append_snippet
 
         gco = self.export_gcode(preamble=preamble, postamble=postamble, to_file=True)
         if gco == 'fail':
@@ -2154,7 +2161,7 @@ class CNCJobObject(FlatCAMObj, CNCjob):
         if preamble == '':
             preamble = self.app.defaults["cncjob_prepend"]
         if postamble == '':
-            preamble = self.app.defaults["cncjob_append"]
+            postamble = self.app.defaults["cncjob_append"]
 
         try:
             if self.special_group:
@@ -2180,7 +2187,6 @@ class CNCJobObject(FlatCAMObj, CNCjob):
 
         gcode = ''
         if include_header is False:
-            g = preamble
             # detect if using multi-tool and make the Gcode summation correctly for each case
             if self.multitool is True:
                 for tooluid_key in self.cnc_tools:
@@ -2191,7 +2197,7 @@ class CNCJobObject(FlatCAMObj, CNCjob):
             else:
                 gcode += self.gcode
 
-            g = g + gcode + postamble
+            g = preamble + '\n' + gcode + '\n' + postamble
         else:
             # search for the GCode beginning which is usually a G20 or G21
             # fix so the preamble gets inserted in between the comments header and the actual start of GCODE
@@ -2241,39 +2247,52 @@ class CNCJobObject(FlatCAMObj, CNCjob):
                             break
 
             if hpgl:
-                processed_gcode = ''
+                processed_body_gcode = ''
                 pa_re = re.compile(r"^PA\s*(-?\d+\.\d*),?\s*(-?\d+\.\d*)*;?$")
+
+                # process body gcode
                 for gline in gcode.splitlines():
                     match = pa_re.search(gline)
                     if match:
                         x_int = int(float(match.group(1)))
                         y_int = int(float(match.group(2)))
                         new_line = 'PA%d,%d;\n' % (x_int, y_int)
-                        processed_gcode += new_line
+                        processed_body_gcode += new_line
                     else:
-                        processed_gcode += gline + '\n'
+                        processed_body_gcode += gline + '\n'
 
-                gcode = processed_gcode
-                g = self.gc_header + '\n' + preamble + '\n' + gcode + postamble + end_gcode
+                gcode = processed_body_gcode
+                g = self.gc_header + '\n' + self.gc_start + '\n' + preamble + '\n' + \
+                    gcode + '\n' + postamble + end_gcode
             else:
-                try:
-                    g_idx = gcode.index('G94')
-                    if preamble != '' and postamble != '':
-                        g = self.gc_header + gcode[:g_idx + 3] + '\n' + preamble + '\n' + \
-                            gcode[(g_idx + 3):] + postamble + end_gcode
-                    elif preamble == '':
-                        g = self.gc_header + gcode[:g_idx + 3] + '\n' + \
-                            gcode[(g_idx + 3):] + postamble + end_gcode
-                    elif postamble == '':
-                        g = self.gc_header + gcode[:g_idx + 3] + '\n' + preamble + '\n' + \
-                            gcode[(g_idx + 3):] + end_gcode
-                    else:
-                        g = self.gc_header + gcode[:g_idx + 3] + gcode[(g_idx + 3):] + end_gcode
-                except ValueError:
-                    self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                         _("G-code does not have a G94 code.\n"
-                                           "Append Code snippet will not be used.."))
-                    g = self.gc_header + '\n' + gcode + postamble + end_gcode
+                # try:
+                #     g_idx = gcode.index('G94')
+                #     if preamble != '' and postamble != '':
+                #         g = self.gc_header + gcode[:g_idx + 3] + '\n' + preamble + '\n' + \
+                #             gcode[(g_idx + 3):] + postamble + end_gcode
+                #     elif preamble == '':
+                #         g = self.gc_header + gcode[:g_idx + 3] + '\n' + \
+                #             gcode[(g_idx + 3):] + postamble + end_gcode
+                #     elif postamble == '':
+                #         g = self.gc_header + gcode[:g_idx + 3] + '\n' + preamble + '\n' + \
+                #             gcode[(g_idx + 3):] + end_gcode
+                #     else:
+                #         g = self.gc_header + gcode[:g_idx + 3] + gcode[(g_idx + 3):] + end_gcode
+                # except ValueError:
+                #     self.app.inform.emit('[ERROR_NOTCL] %s' %
+                #                          _("G-code does not have a G94 code.\n"
+                #                            "Append Code snippet will not be used.."))
+                #     g = self.gc_header + '\n' + gcode + postamble + end_gcode
+                g = ''
+                if preamble != '' and postamble != '':
+                    g = self.gc_header + self.gc_start + '\n' + preamble + '\n' + gcode + '\n' + \
+                        postamble + '\n' + end_gcode
+                if preamble == '':
+                    g = self.gc_header + self.gc_start + '\n' + gcode + '\n' + postamble + '\n' + end_gcode
+                if postamble == '':
+                    g = self.gc_header + self.gc_start + '\n' + preamble + '\n' + gcode + '\n' + end_gcode
+                if preamble == '' and postamble == '':
+                    g = self.gc_header + self.gc_start + '\n' + gcode + '\n' + end_gcode
 
         # if toolchange custom is used, replace M6 code with the code from the Toolchange Custom Text box
         # if self.ui.toolchange_cb.get_value() is True:
@@ -2490,10 +2509,10 @@ class CNCJobObject(FlatCAMObj, CNCjob):
             if self.app.is_legacy is False:
                 self.annotation.clear(update=True)
 
-        # Annotaions shapes plotting
+        # Annotations shapes plotting
         try:
             if self.app.is_legacy is False:
-                if self.ui.annotation_cb.get_value() and self.ui.plot_cb.get_value():
+                if self.ui.annotation_cb.get_value() and visible:
                     self.plot_annotations(obj=self, visible=True)
                 else:
                     self.plot_annotations(obj=self, visible=False)
@@ -2502,20 +2521,28 @@ class CNCJobObject(FlatCAMObj, CNCjob):
             if self.app.is_legacy is False:
                 self.annotation.clear(update=True)
 
-    def on_annotation_change(self):
+    def on_annotation_change(self, val):
         """
         Handler for toggling the annotation display by clicking a checkbox.
         :return:
         """
 
         if self.app.is_legacy is False:
-            if self.ui.annotation_cb.get_value():
-                self.text_col.enabled = True
-            else:
-                self.text_col.enabled = False
-            # kind = self.ui.cncplot_method_combo.get_value()
-            # self.plot(kind=kind)
-            self.annotation.redraw()
+            # self.text_col.visible = True if val == 2 else False
+            # self.plot(kind=self.ui.cncplot_method_combo.get_value())
+            # Annotations shapes plotting
+            try:
+                if self.app.is_legacy is False:
+                    if val and self.ui.plot_cb.get_value():
+                        self.plot_annotations(obj=self, visible=True)
+                    else:
+                        self.plot_annotations(obj=self, visible=False)
+
+            except (ObjectDeleted, AttributeError):
+                if self.app.is_legacy is False:
+                    self.annotation.clear(update=True)
+
+            # self.annotation.redraw()
         else:
             kind = self.ui.cncplot_method_combo.get_value()
             self.plot(kind=kind)

+ 2 - 6
appObjects/FlatCAMDocument.py

@@ -67,13 +67,9 @@ class DocumentObject(FlatCAMObj):
 
         # Show/Hide Advanced Options
         if self.app.defaults["global_app_level"] == 'b':
-            self.ui.level.setText(_(
-                '<span style="color:green;"><b>Basic</b></span>'
-            ))
+            self.ui.level.setText('<span style="color:green;"><b>%s</b></span>' % _("Basic"))
         else:
-            self.ui.level.setText(_(
-                '<span style="color:red;"><b>Advanced</b></span>'
-            ))
+            self.ui.level.setText('<span style="color:red;"><b>%s</b></span>' % _("Advanced"))
 
         self.document_editor_tab = AppTextEditor(app=self.app)
         stylesheet = """

+ 11 - 14
appObjects/FlatCAMExcellon.py

@@ -11,16 +11,13 @@
 # ##########################################################
 
 
-from shapely.geometry import Point, LineString
-
-from copy import deepcopy
+from shapely.geometry import LineString
 
 from appParsers.ParseExcellon import Excellon
 from appObjects.FlatCAMObj import *
 
 import itertools
 import numpy as np
-from collections import defaultdict
 
 import gettext
 import appTranslation as fcTranslate
@@ -1016,10 +1013,10 @@ class ExcellonObject(FlatCAMObj, Excellon):
             # in case that the tool used has the same diameter with the hole, and since the maximum resolution
             # for FlatCAM is 6 decimals,
             # we add a tenth of the minimum value, meaning 0.0000001, which from our point of view is "almost zero"
-            for tool in tools:
-                for slot in self.tools[tool]['slots']:
+            for m_tool in tools:
+                for slot in self.tools[m_tool]['slots']:
                     toolstable_tool = float('%.*f' % (self.decimals, float(tooldia)))
-                    file_tool = float('%.*f' % (self.decimals, float(self.tools[tool]["tooldia"])))
+                    file_tool = float('%.*f' % (self.decimals, float(self.tools[m_tool]["tooldia"])))
 
                     # I add the 0.0001 value to account for the rounding error in converting from IN to MM and reverse
                     # for the file_tool (tooldia actually)
@@ -1164,8 +1161,8 @@ class ExcellonObject(FlatCAMObj, Excellon):
                     r_color[3] = 1
 
                     new_color = '#'
-                    for idx in range(len(r_color)):
-                        new_color += '%x' % int(r_color[idx] * 255)
+                    for idx_c in range(len(r_color)):
+                        new_color += '%x' % int(r_color[idx_c] * 255)
                     # do it until a valid color is generated
                     # a valid color has the # symbol, another 6 chars for the color and the last 2 chars for alpha
                     # for a total of 9 chars
@@ -1278,21 +1275,21 @@ class ExcellonObject(FlatCAMObj, Excellon):
             for option in exc.options:
                 if option != 'name':
                     try:
-                        exc_final.options[option] = exc.options[option]
+                        exc_final.options[option] = deepcopy(exc.options[option])
                     except Exception:
                         exc.app.log.warning("Failed to copy option.", option)
 
             for tool in exc.tools:
                 toolid += 1
-                new_tools[toolid] = exc.tools[tool]
+                new_tools[toolid] = deepcopy(exc.tools[tool])
 
             exc_final.tools = deepcopy(new_tools)
             # add the zeros and units to the exc_final object
-            exc_final.zeros = exc.zeros
-            exc_final.units = exc.units
+            exc_final.zeros = deepcopy(exc.zeros)
+            exc_final.units = deepcopy(exc.units)
             total_geo += exc.solid_geometry
 
-        exc_final.solid_geometry = total_geo
+        exc_final.solid_geometry = deepcopy(total_geo)
 
         fused_tools_dict = {}
         if exc_final.tools and fuse_tools:

+ 362 - 108
appObjects/FlatCAMGeometry.py

@@ -10,10 +10,10 @@
 # File modified by: Marius Stanciu                         #
 # ##########################################################
 
-from shapely.geometry import Polygon, MultiPolygon, MultiLineString, LineString, LinearRing
+from shapely.geometry import MultiLineString, LineString, LinearRing, box
 import shapely.affinity as affinity
 
-from camlib import Geometry
+from camlib import Geometry, grace
 
 from appObjects.FlatCAMObj import *
 
@@ -25,6 +25,8 @@ import traceback
 from collections import defaultdict
 from functools import reduce
 
+import simplejson as json
+
 import gettext
 import appTranslation as fcTranslate
 import builtins
@@ -41,6 +43,7 @@ class GeometryObject(FlatCAMObj, Geometry):
     """
     optionChanged = QtCore.pyqtSignal(str)
     builduiSig = QtCore.pyqtSignal()
+    launch_job = QtCore.pyqtSignal()
 
     ui_type = GeometryObjectUI
 
@@ -125,7 +128,7 @@ class GeometryObject(FlatCAMObj, Geometry):
         self.sel_tools = {}
 
         self.offset_item_options = ["Path", "In", "Out", "Custom"]
-        self.type_item_options = [_("Iso"), _("Rough"), _("Finish")]
+        self.type_item_options = ['Iso', 'Rough', 'Finish']
         self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"]
 
         # flag to store if the V-Shape tool is selected in self.ui.geo_tools_table
@@ -200,27 +203,39 @@ class GeometryObject(FlatCAMObj, Geometry):
             self.ui.geo_tools_table.setItem(row_idx, 1, dia_item)  # Diameter
 
             # -------------------- OFFSET   ------------------------------------- #
-            offset_item = FCComboBox()
+            offset_item = FCComboBox(policy=False)
             for item in self.offset_item_options:
                 offset_item.addItem(item)
             idx = offset_item.findText(tooluid_value['offset'])
-            offset_item.setCurrentIndex(idx)
+            # protection against having this translated or loading a project with translated values
+            if idx == -1:
+                offset_item.setCurrentIndex(0)
+            else:
+                offset_item.setCurrentIndex(idx)
             self.ui.geo_tools_table.setCellWidget(row_idx, 2, offset_item)
 
             # -------------------- TYPE     ------------------------------------- #
-            type_item = FCComboBox()
+            type_item = FCComboBox(policy=False)
             for item in self.type_item_options:
                 type_item.addItem(item)
             idx = type_item.findText(tooluid_value['type'])
-            type_item.setCurrentIndex(idx)
+            # protection against having this translated or loading a project with translated values
+            if idx == -1:
+                type_item.setCurrentIndex(0)
+            else:
+                type_item.setCurrentIndex(idx)
             self.ui.geo_tools_table.setCellWidget(row_idx, 3, type_item)
 
             # -------------------- TOOL TYPE ------------------------------------- #
-            tool_type_item = FCComboBox()
+            tool_type_item = FCComboBox(policy=False)
             for item in self.tool_type_item_options:
                 tool_type_item.addItem(item)
             idx = tool_type_item.findText(tooluid_value['tool_type'])
-            tool_type_item.setCurrentIndex(idx)
+            # protection against having this translated or loading a project with translated values
+            if idx == -1:
+                tool_type_item.setCurrentIndex(0)
+            else:
+                tool_type_item.setCurrentIndex(idx)
             self.ui.geo_tools_table.setCellWidget(row_idx, 4, tool_type_item)
 
             # -------------------- TOOL UID   ------------------------------------- #
@@ -442,6 +457,8 @@ class GeometryObject(FlatCAMObj, Geometry):
             "polish": self.ui.polish_cb,
             "polish_dia": self.ui.polish_dia_entry,
             "polish_pressure": self.ui.polish_pressure_entry,
+            "polish_travelz": self.ui.polish_travelz_entry,
+            "polish_margin": self.ui.polish_margin_entry,
             "polish_overlap": self.ui.polish_over_entry,
             "polish_method": self.ui.polish_method_combo,
         })
@@ -514,10 +531,10 @@ class GeometryObject(FlatCAMObj, Geometry):
                 new_data = deepcopy(self.default_data)
                 self.tools.update({
                     self.tooluid: {
-                        'tooldia': float('%.*f' % (self.decimals, float(toold))),
+                        'tooldia': self.app.dec_format(float(toold), self.decimals),
                         'offset': 'Path',
                         'offset_value': 0.0,
-                        'type': _('Rough'),
+                        'type': 'Rough',
                         'tool_type': self.tool_type,
                         'data': new_data,
                         'solid_geometry': self.solid_geometry
@@ -552,14 +569,14 @@ class GeometryObject(FlatCAMObj, Geometry):
 
         self.ui.geo_tools_table.setupContextMenu()
         self.ui.geo_tools_table.addContextMenu(
-            _("Add from Tool DB"), self.on_tool_add_from_db_clicked,
+            _("Pick from DB"), self.on_tool_add_from_db_clicked,
             icon=QtGui.QIcon(self.app.resource_location + "/plus16.png"))
         self.ui.geo_tools_table.addContextMenu(
             _("Copy"), self.on_tool_copy,
             icon=QtGui.QIcon(self.app.resource_location + "/copy16.png"))
         self.ui.geo_tools_table.addContextMenu(
             _("Delete"), lambda: self.on_tool_delete(all_tools=None),
-            icon=QtGui.QIcon(self.app.resource_location + "/delete32.png"))
+            icon=QtGui.QIcon(self.app.resource_location + "/trash16.png"))
 
         # Show/Hide Advanced Options
         if self.app.defaults["global_app_level"] == 'b':
@@ -570,8 +587,7 @@ class GeometryObject(FlatCAMObj, Geometry):
             # self.ui.geo_tools_table.setColumnHidden(4, True)
             self.ui.addtool_entry_lbl.hide()
             self.ui.addtool_entry.hide()
-            self.ui.addtool_btn.hide()
-            self.ui.copytool_btn.hide()
+            self.ui.search_and_add_btn.hide()
             self.ui.deltool_btn.hide()
             # self.ui.endz_label.hide()
             # self.ui.endz_entry.hide()
@@ -625,6 +641,8 @@ class GeometryObject(FlatCAMObj, Geometry):
 
         self.ui.geo_tools_table.drag_drop_sig.connect(self.rebuild_ui)
 
+        self.launch_job.connect(self.mtool_gen_cncjob)
+
     def on_properties(self, state):
         if state:
             self.ui.properties_frame.show()
@@ -729,11 +747,9 @@ class GeometryObject(FlatCAMObj, Geometry):
                 self.ui.geo_tools_table.cellWidget(row, col).currentIndexChanged.connect(
                     self.on_tooltable_cellwidget_change)
 
-        # I use lambda's because the connected functions have parameters that could be used in certain scenarios
-        self.ui.addtool_btn.clicked.connect(lambda: self.on_tool_add())
+        self.ui.search_and_add_btn.clicked.connect(self.on_tool_add)
 
-        self.ui.copytool_btn.clicked.connect(lambda: self.on_tool_copy())
-        self.ui.deltool_btn.clicked.connect(lambda: self.on_tool_delete())
+        self.ui.deltool_btn.clicked.connect(self.on_tool_delete)
 
         self.ui.geo_tools_table.clicked.connect(self.on_row_selection_change)
         self.ui.geo_tools_table.horizontalHeader().sectionClicked.connect(self.on_toggle_all_rows)
@@ -793,12 +809,7 @@ class GeometryObject(FlatCAMObj, Geometry):
                     pass
 
         try:
-            self.ui.addtool_btn.clicked.disconnect()
-        except (TypeError, AttributeError):
-            pass
-
-        try:
-            self.ui.copytool_btn.clicked.disconnect()
+            self.ui.search_and_add_btn.clicked.disconnect()
         except (TypeError, AttributeError):
             pass
 
@@ -1004,19 +1015,156 @@ class GeometryObject(FlatCAMObj, Geometry):
 
         self.ui_connect()
 
-    def on_tool_add(self, dia=None, new_geo=None):
+    def on_tool_add(self, clicked_state, dia=None, new_geo=None):
+        log.debug("GeometryObject.on_add_tool()")
+
         self.ui_disconnect()
 
-        self.units = self.app.defaults['units'].upper()
+        filename = self.app.tools_database_path()
+
+        tool_dia = dia if dia is not None else self.ui.addtool_entry.get_value()
+
+        # construct a list of all 'tooluid' in the self.iso_tools
+        tool_uid_list = [int(tooluid_key) for tooluid_key in self.tools]
+
+        # find maximum from the temp_uid, add 1 and this is the new 'tooluid'
+        max_uid = 0 if not tool_uid_list else max(tool_uid_list)
+        tooluid = int(max_uid) + 1
+
+        new_tools_dict = deepcopy(self.default_data)
+        updated_tooldia = None
+
+        # determine the new tool diameter
+        if tool_dia is None or tool_dia == 0:
+            self.build_ui()
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Please enter a tool diameter with non-zero value, "
+                                                          "in Float format."))
+            self.ui_connect()
+            return
+        truncated_tooldia = self.app.dec_format(tool_dia, self.decimals)
+
+        # load the database tools from the file
+        try:
+            with open(filename) as f:
+                tools = f.read()
+        except IOError:
+            self.app.log.error("Could not load tools DB file.")
+            self.app.inform.emit('[ERROR] %s' % _("Could not load Tools DB file."))
+            self.ui_connect()
+            self.on_tool_default_add(dia=tool_dia)
+            return
+
+        try:
+            # store here the tools from Tools Database when searching in Tools Database
+            tools_db_dict = json.loads(tools)
+        except Exception:
+            e = sys.exc_info()[0]
+            self.app.log.error(str(e))
+            self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file."))
+            self.ui_connect()
+            self.on_tool_default_add(dia=tool_dia)
+            return
+
+        tool_found = 0
+
+        offset = 'Path'
+        offset_val = 0.0
+        typ = 'Rough'
+        tool_type = 'C1'
+        # look in database tools
+        for db_tool, db_tool_val in tools_db_dict.items():
+            offset = db_tool_val['offset']
+            offset_val = db_tool_val['offset_value']
+            typ = db_tool_val['type']
+            tool_type = db_tool_val['tool_type']
+
+            db_tooldia = db_tool_val['tooldia']
+            low_limit = float(db_tool_val['data']['tol_min'])
+            high_limit = float(db_tool_val['data']['tol_max'])
+
+            # we need only tool marked for Milling Tool (Geometry Object)
+            if db_tool_val['data']['tool_target'] != 1:     # _('Milling')
+                continue
+
+            # if we find a tool with the same diameter in the Tools DB just update it's data
+            if truncated_tooldia == db_tooldia:
+                tool_found += 1
+                for d in db_tool_val['data']:
+                    if d.find('tools_mill_') == 0:
+                        new_tools_dict[d] = db_tool_val['data'][d]
+                    elif d.find('tools_') == 0:
+                        # don't need data for other App Tools; this tests after 'tools_mill_'
+                        continue
+                    else:
+                        new_tools_dict[d] = db_tool_val['data'][d]
+            # search for a tool that has a tolerance that the tool fits in
+            elif high_limit >= truncated_tooldia >= low_limit:
+                tool_found += 1
+                updated_tooldia = db_tooldia
+                for d in db_tool_val['data']:
+                    if d.find('tools_iso') == 0:
+                        new_tools_dict[d] = db_tool_val['data'][d]
+                    elif d.find('tools_') == 0:
+                        # don't need data for other App Tools; this tests after 'tools_drill_'
+                        continue
+                    else:
+                        new_tools_dict[d] = db_tool_val['data'][d]
+
+        # test we found a suitable tool in Tools Database or if multiple ones
+        if tool_found == 0:
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Tool not in Tools Database. Adding a default tool."))
+            self.on_tool_default_add(dia=tool_dia, new_geo=new_geo)
+            self.ui_connect()
+            return
+
+        if tool_found > 1:
+            self.app.inform.emit(
+                '[WARNING_NOTCL] %s' % _("Cancelled.\n"
+                                         "Multiple tools for one tool diameter found in Tools Database."))
+            self.ui_connect()
+            return
+
+        new_tdia = deepcopy(updated_tooldia) if updated_tooldia is not None else deepcopy(truncated_tooldia)
+        self.tools.update({
+            tooluid: {
+                'tooldia': new_tdia,
+                'offset': deepcopy(offset),
+                'offset_value': deepcopy(offset_val),
+                'type': deepcopy(typ),
+                'tool_type': deepcopy(tool_type),
+                'data': deepcopy(new_tools_dict),
+                'solid_geometry': self.solid_geometry
+            }
+        })
+        self.ui_connect()
+        self.build_ui()
+
+        # select the tool just added
+        for row in range(self.ui.geo_tools_table.rowCount()):
+            if int(self.ui.geo_tools_table.item(row, 5).text()) == tooluid:
+                self.ui.geo_tools_table.selectRow(row)
+                break
+
+        # update the UI form
+        self.update_ui()
+
+        # if there is at least one tool left in the Tools Table, enable the parameters GUI
+        if self.ui.geo_tools_table.rowCount() != 0:
+            self.ui.geo_param_frame.setDisabled(False)
+
+        self.app.inform.emit('[success] %s' % _("New tool added to Tool Table from Tools Database."))
+
+    def on_tool_default_add(self, dia=None, new_geo=None, muted=None):
+        self.ui_disconnect()
 
-        tooldia = dia if dia is not None else float(self.ui.addtool_entry.get_value())
+        tooldia = dia if dia is not None else self.ui.addtool_entry.get_value()
         tool_uid_list = [int(tooluid_key) for tooluid_key in self.tools]
 
         # find maximum from the temp_uid, add 1 and this is the new 'tooluid'
         max_uid = max(tool_uid_list) if tool_uid_list else 0
-        self.tooluid = max_uid + 1
+        self.tooluid = int(max_uid) + 1
 
-        tooldia = float('%.*f' % (self.decimals, tooldia))
+        tooldia = self.app.dec_format(tooldia, self.decimals)
 
         # here we actually add the new tool; if there is no tool in the tool table we add a tool with default data
         # otherwise we add a tool with data copied from last tool
@@ -1051,7 +1199,7 @@ class GeometryObject(FlatCAMObj, Geometry):
                     'tooldia': tooldia,
                     'offset': 'Path',
                     'offset_value': 0.0,
-                    'type': _('Rough'),
+                    'type': 'Rough',
                     'tool_type': 'C1',
                     'data': deepcopy(self.default_data),
                     'solid_geometry': self.solid_geometry
@@ -1070,7 +1218,8 @@ class GeometryObject(FlatCAMObj, Geometry):
             pass
         self.ser_attrs.append('tools')
 
-        self.app.inform.emit('[success] %s' % _("Tool added in Tool Table."))
+        if muted is None:
+            self.app.inform.emit('[success] %s' % _("Tool added in Tool Table."))
         self.ui_connect()
         self.build_ui()
 
@@ -1090,7 +1239,9 @@ class GeometryObject(FlatCAMObj, Geometry):
             if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
                 self.app.ui.plot_tab_area.setCurrentWidget(self.app.tools_db_tab)
                 break
-        self.app.on_tools_database()
+        ret_val = self.app.on_tools_database()
+        if ret_val == 'fail':
+            return
         self.app.tools_db_tab.ok_to_add = True
         self.app.tools_db_tab.ui.buttons_frame.hide()
         self.app.tools_db_tab.ui.add_tool_from_db.show()
@@ -1251,7 +1402,11 @@ class GeometryObject(FlatCAMObj, Geometry):
         self.ui_connect()
         self.builduiSig.emit()
 
-    def on_tool_delete(self, all_tools=None):
+    def on_tool_delete(self, clicked_signal, all_tools=None):
+        """
+        It's important to keep the not clicked_signal parameter otherwise the signal will go to the all_tools
+        parameter and I might get all the tool deleted
+        """
         self.ui_disconnect()
 
         if all_tools is None:
@@ -1432,8 +1587,8 @@ class GeometryObject(FlatCAMObj, Geometry):
                 elif cw_col == 3:
                     # force toolpath type as 'Iso' if the tool type is V-Shape
                     if self.ui.geo_tools_table.cellWidget(cw_row, 4).currentText() == 'V':
-                        tooluid_value['type'] = _('Iso')
-                        idx = self.ui.geo_tools_table.cellWidget(cw_row, 3).findText(_('Iso'))
+                        tooluid_value['type'] = 'Iso'
+                        idx = self.ui.geo_tools_table.cellWidget(cw_row, 3).findText('Iso')
                         self.ui.geo_tools_table.cellWidget(cw_row, 3).setCurrentIndex(idx)
                     else:
                         tooluid_value['type'] = cb_txt
@@ -1442,7 +1597,7 @@ class GeometryObject(FlatCAMObj, Geometry):
 
                     # if the tool_type selected is V-Shape then autoselect the toolpath type as Iso
                     if cb_txt == 'V':
-                        idx = self.ui.geo_tools_table.cellWidget(cw_row, 3).findText(_('Iso'))
+                        idx = self.ui.geo_tools_table.cellWidget(cw_row, 3).findText('Iso')
                         self.ui.geo_tools_table.cellWidget(cw_row, 3).setCurrentIndex(idx)
                     else:
                         self.ui.cutz_entry.set_value(self.old_cutz)
@@ -1833,7 +1988,7 @@ class GeometryObject(FlatCAMObj, Geometry):
             except AttributeError:
                 pass
 
-    def on_generatecnc_button_click(self, *args):
+    def on_generatecnc_button_click(self):
         log.debug("Generating CNCJob from Geometry ...")
         self.app.defaults.report_usage("geometry_on_generatecnc_button")
 
@@ -1846,7 +2001,7 @@ class GeometryObject(FlatCAMObj, Geometry):
             if self.special_group:
                 self.app.inform.emit(
                     '[WARNING_NOTCL] %s %s %s.' %
-                    (_("This Geometry can't be processed because it is"), str(self.special_group), _("geometry"))
+                    (_("This Geometry can't be processed because it is"), str(self.special_group), _("Geometry"))
                 )
                 return
         except AttributeError:
@@ -1862,7 +2017,11 @@ class GeometryObject(FlatCAMObj, Geometry):
                         self.sel_tools.update({
                             tooluid: deepcopy(tooluid_value)
                         })
-            self.mtool_gen_cncjob()
+
+            if self.ui.polish_cb.get_value():
+                self.on_polish()
+            else:
+                self.mtool_gen_cncjob()
             self.ui.geo_tools_table.clearSelection()
 
         elif self.ui.geo_tools_table.rowCount() == 1:
@@ -1873,9 +2032,11 @@ class GeometryObject(FlatCAMObj, Geometry):
                     self.sel_tools.update({
                         tooluid: deepcopy(tooluid_value)
                     })
-            self.mtool_gen_cncjob()
+            if self.ui.polish_cb.get_value():
+                self.on_polish()
+            else:
+                self.mtool_gen_cncjob()
             self.ui.geo_tools_table.clearSelection()
-
         else:
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed. No tool selected in the tool table ..."))
 
@@ -1955,7 +2116,7 @@ class GeometryObject(FlatCAMObj, Geometry):
                 tool_cnt += 1
 
                 dia_cnc_dict = deepcopy(tools_dict[tooluid_key])
-                tooldia_val = float('%.*f' % (self.decimals, float(tools_dict[tooluid_key]['tooldia'])))
+                tooldia_val = app_obj.dec_format(float(tools_dict[tooluid_key]['tooldia']), self.decimals)
                 dia_cnc_dict.update({
                     'tooldia': tooldia_val
                 })
@@ -1972,12 +2133,12 @@ class GeometryObject(FlatCAMObj, Geometry):
                         try:
                             offset_value = float(self.ui.tool_offset_entry.get_value().replace(',', '.'))
                         except ValueError:
-                            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number."))
+                            app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number."))
                             return
                     if offset_value:
                         tool_offset = float(offset_value)
                     else:
-                        self.app.inform.emit(
+                        app_obj.inform.emit(
                             '[WARNING] %s' % _("Tool Offset is selected in Tool Table but no value is provided.\n"
                                                "Add a Tool Offset or change the Offset Type.")
                         )
@@ -2020,10 +2181,13 @@ class GeometryObject(FlatCAMObj, Geometry):
                 job_obj.options['type'] = 'Geometry'
                 job_obj.options['tool_dia'] = tooldia_val
 
+                tool_lst = list(tools_dict.keys())
+                is_first = True if tooluid_key == tool_lst[0] else False
+
                 # it seems that the tolerance needs to be a lot lower value than 0.01 and it was hardcoded initially
                 # to a value of 0.0005 which is 20 times less than 0.01
                 tol = float(self.app.defaults['global_tolerance']) / 20
-                res = job_obj.generate_from_geometry_2(
+                res, start_gcode = job_obj.generate_from_geometry_2(
                     self, tooldia=tooldia_val, offset=tool_offset, tolerance=tol,
                     z_cut=z_cut, z_move=z_move,
                     feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid,
@@ -2032,13 +2196,15 @@ class GeometryObject(FlatCAMObj, Geometry):
                     extracut=extracut, extracut_length=extracut_length, startz=startz, endz=endz, endxy=endxy,
                     toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy,
                     pp_geometry_name=pp_geometry_name,
-                    tool_no=tool_cnt)
+                    tool_no=tool_cnt, is_first=is_first)
 
                 if res == 'fail':
                     log.debug("GeometryObject.mtool_gen_cncjob() --> generate_from_geometry2() failed")
                     return 'fail'
-                else:
-                    dia_cnc_dict['gcode'] = res
+
+                dia_cnc_dict['gcode'] = res
+                if start_gcode != '':
+                    job_obj.gc_start = start_gcode
 
                 total_gcode += res
 
@@ -2048,24 +2214,23 @@ class GeometryObject(FlatCAMObj, Geometry):
 
                 self.app.inform.emit('[success] %s' % _("G-Code parsing in progress..."))
                 dia_cnc_dict['gcode_parsed'] = job_obj.gcode_parse()
-                self.app.inform.emit('[success] %s' % _("G-Code parsing finished..."))
+                app_obj.inform.emit('[success] %s' % _("G-Code parsing finished..."))
 
-                # TODO this serve for bounding box creation only; should be optimized
                 # commented this; there is no need for the actual GCode geometry - the original one will serve as well
                 # for bounding box values
                 # dia_cnc_dict['solid_geometry'] = unary_union([geo['geom'] for geo in dia_cnc_dict['gcode_parsed']])
                 try:
                     dia_cnc_dict['solid_geometry'] = tool_solid_geometry
-                    self.app.inform.emit('[success] %s...' % _("Finished G-Code processing"))
+                    app_obj.inform.emit('[success] %s...' % _("Finished G-Code processing"))
                 except Exception as er:
-                    self.app.inform.emit('[ERROR] %s: %s' % (_("G-Code processing failed with error"), str(er)))
+                    app_obj.inform.emit('[ERROR] %s: %s' % (_("G-Code processing failed with error"), str(er)))
 
                 job_obj.cnc_tools.update({
                     tooluid_key: deepcopy(dia_cnc_dict)
                 })
                 dia_cnc_dict.clear()
 
-            job_obj.source_file = total_gcode
+            job_obj.source_file = job_obj.gc_start + total_gcode
 
         # Object initialization function for app.app_obj.new_object()
         # RUNNING ON SEPARATE THREAD!
@@ -2102,15 +2267,14 @@ class GeometryObject(FlatCAMObj, Geometry):
                     if self.tools[tooluid_key]['solid_geometry'] is None:
                         a += 1
                 if a == len(self.tools):
-                    self.app.inform.emit('[ERROR_NOTCL] %s...' % _('Cancelled. Empty file, it has no geometry'))
+                    app_obj.inform.emit('[ERROR_NOTCL] %s...' % _('Cancelled. Empty file, it has no geometry'))
                     return 'fail'
 
             total_gcode = ''
             for tooluid_key in list(tools_dict.keys()):
                 tool_cnt += 1
                 dia_cnc_dict = deepcopy(tools_dict[tooluid_key])
-                tooldia_val = float('%.*f' % (self.decimals, float(tools_dict[tooluid_key]['tooldia'])))
-
+                tooldia_val = app_obj.dec_format(float(tools_dict[tooluid_key]['tooldia']), self.decimals)
                 dia_cnc_dict.update({
                     'tooldia': tooldia_val
                 })
@@ -2123,7 +2287,7 @@ class GeometryObject(FlatCAMObj, Geometry):
                 #         current_uid = int(k)
                 #         break
 
-                if dia_cnc_dict['offset'] == 'in':
+                if dia_cnc_dict['offset'].lower() == 'in':
                     tool_offset = -tooldia_val / 2
                 elif dia_cnc_dict['offset'].lower() == 'out':
                     tool_offset = tooldia_val / 2
@@ -2144,27 +2308,27 @@ class GeometryObject(FlatCAMObj, Geometry):
                     'offset_value': tool_offset
                 })
 
-                z_cut = tools_dict[tooluid_key]['data']["cutz"]
-                z_move = tools_dict[tooluid_key]['data']["travelz"]
-                feedrate = tools_dict[tooluid_key]['data']["feedrate"]
-                feedrate_z = tools_dict[tooluid_key]['data']["feedrate_z"]
-                feedrate_rapid = tools_dict[tooluid_key]['data']["feedrate_rapid"]
-                multidepth = tools_dict[tooluid_key]['data']["multidepth"]
-                extracut = tools_dict[tooluid_key]['data']["extracut"]
-                extracut_length = tools_dict[tooluid_key]['data']["extracut_length"]
-                depthpercut = tools_dict[tooluid_key]['data']["depthperpass"]
-                toolchange = tools_dict[tooluid_key]['data']["toolchange"]
-                toolchangez = tools_dict[tooluid_key]['data']["toolchangez"]
-                toolchangexy = tools_dict[tooluid_key]['data']["toolchangexy"]
-                startz = tools_dict[tooluid_key]['data']["startz"]
-                endz = tools_dict[tooluid_key]['data']["endz"]
-                endxy = self.options["endxy"]
-                spindlespeed = tools_dict[tooluid_key]['data']["spindlespeed"]
-                dwell = tools_dict[tooluid_key]['data']["dwell"]
-                dwelltime = tools_dict[tooluid_key]['data']["dwelltime"]
-                pp_geometry_name = tools_dict[tooluid_key]['data']["ppname_g"]
-
-                spindledir = self.app.defaults['geometry_spindledir']
+                # z_cut = tools_dict[tooluid_key]['data']["cutz"]
+                # z_move = tools_dict[tooluid_key]['data']["travelz"]
+                # feedrate = tools_dict[tooluid_key]['data']["feedrate"]
+                # feedrate_z = tools_dict[tooluid_key]['data']["feedrate_z"]
+                # feedrate_rapid = tools_dict[tooluid_key]['data']["feedrate_rapid"]
+                # multidepth = tools_dict[tooluid_key]['data']["multidepth"]
+                # extracut = tools_dict[tooluid_key]['data']["extracut"]
+                # extracut_length = tools_dict[tooluid_key]['data']["extracut_length"]
+                # depthpercut = tools_dict[tooluid_key]['data']["depthperpass"]
+                # toolchange = tools_dict[tooluid_key]['data']["toolchange"]
+                # toolchangez = tools_dict[tooluid_key]['data']["toolchangez"]
+                # toolchangexy = tools_dict[tooluid_key]['data']["toolchangexy"]
+                # startz = tools_dict[tooluid_key]['data']["startz"]
+                # endz = tools_dict[tooluid_key]['data']["endz"]
+                # endxy = self.options["endxy"]
+                # spindlespeed = tools_dict[tooluid_key]['data']["spindlespeed"]
+                # dwell = tools_dict[tooluid_key]['data']["dwell"]
+                # dwelltime = tools_dict[tooluid_key]['data']["dwelltime"]
+                # pp_geometry_name = tools_dict[tooluid_key]['data']["ppname_g"]
+                #
+                # spindledir = self.app.defaults['geometry_spindledir']
                 tool_solid_geometry = self.tools[tooluid_key]['solid_geometry']
 
                 job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"]
@@ -2196,11 +2360,10 @@ class GeometryObject(FlatCAMObj, Geometry):
                 if start_gcode != '':
                     job_obj.gc_start = start_gcode
 
-                self.app.inform.emit('[success] %s' % _("G-Code parsing in progress..."))
+                app_obj.inform.emit('[success] %s' % _("G-Code parsing in progress..."))
                 dia_cnc_dict['gcode_parsed'] = job_obj.gcode_parse()
-                self.app.inform.emit('[success] %s' % _("G-Code parsing finished..."))
+                app_obj.inform.emit('[success] %s' % _("G-Code parsing finished..."))
 
-                # TODO this serve for bounding box creation only; should be optimized
                 # commented this; there is no need for the actual GCode geometry - the original one will serve as well
                 # for bounding box values
                 # geo_for_bound_values = unary_union([
@@ -2208,9 +2371,9 @@ class GeometryObject(FlatCAMObj, Geometry):
                 # ])
                 try:
                     dia_cnc_dict['solid_geometry'] = deepcopy(tool_solid_geometry)
-                    self.app.inform.emit('[success] %s' % _("Finished G-Code processing..."))
+                    app_obj.inform.emit('[success] %s...' % _("Finished G-Code processing"))
                 except Exception as ee:
-                    self.app.inform.emit('[ERROR] %s: %s' % (_("G-Code processing failed with error"), str(ee)))
+                    app_obj.inform.emit('[ERROR] %s: %s' % (_("G-Code processing failed with error"), str(ee)))
 
                 # tell gcode_parse from which point to start drawing the lines depending on what kind of
                 # object is the source of gcode
@@ -2228,11 +2391,13 @@ class GeometryObject(FlatCAMObj, Geometry):
             def job_thread(a_obj):
                 if self.multigeo is False:
                     with self.app.proc_container.new(_("Generating CNC Code")):
-                        if a_obj.app_obj.new_object("cncjob", outname, job_init_single_geometry, plot=plot) != 'fail':
+                        ret_val = a_obj.app_obj.new_object("cncjob", outname, job_init_single_geometry, plot=plot)
+                        if ret_val != 'fail':
                             a_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname))
                 else:
                     with self.app.proc_container.new(_("Generating CNC Code")):
-                        if a_obj.app_obj.new_object("cncjob", outname, job_init_multi_geometry) != 'fail':
+                        ret_val = a_obj.app_obj.new_object("cncjob", outname, job_init_multi_geometry, plot=plot)
+                        if ret_val != 'fail':
                             a_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname))
 
             # Create a promise with the name
@@ -2362,23 +2527,22 @@ class GeometryObject(FlatCAMObj, Geometry):
             # it seems that the tolerance needs to be a lot lower value than 0.01 and it was hardcoded initially
             # to a value of 0.0005 which is 20 times less than 0.01
             tol = float(self.app.defaults['global_tolerance']) / 20
-            res = job_obj.generate_from_geometry_2(self, tooldia=tooldia, offset=offset, tolerance=tol,
-                                                   z_cut=z_cut, z_move=z_move, feedrate=feedrate,
-                                                   feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid,
-                                                   spindlespeed=spindlespeed, dwell=dwell, dwelltime=dwelltime,
-                                                   multidepth=multidepth, depthpercut=depthperpass,
-                                                   toolchange=toolchange, toolchangez=toolchangez,
-                                                   toolchangexy=toolchangexy,
-                                                   extracut=extracut, extracut_length=extracut_length,
-                                                   startz=startz, endz=endz, endxy=endxy,
-                                                   pp_geometry_name=ppname_g)
-
-            job_obj.source_file = res
+            res, start_gcode = job_obj.generate_from_geometry_2(
+                self, tooldia=tooldia, offset=offset, tolerance=tol, z_cut=z_cut, z_move=z_move, feedrate=feedrate,
+                feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid, spindlespeed=spindlespeed, dwell=dwell,
+                dwelltime=dwelltime, multidepth=multidepth, depthpercut=depthperpass, toolchange=toolchange,
+                toolchangez=toolchangez, toolchangexy=toolchangexy, extracut=extracut, extracut_length=extracut_length,
+                startz=startz, endz=endz, endxy=endxy, pp_geometry_name=ppname_g, is_first=True)
+
+            if start_gcode != '':
+                job_obj.gc_start = start_gcode
+
+            job_obj.source_file = start_gcode + res
             # tell gcode_parse from which point to start drawing the lines depending on what kind of object is the
             # source of gcode
             job_obj.toolchange_xy_type = "geometry"
             job_obj.gcode_parse()
-            self.app.inform.emit('[success] %s' % _("Finished G-Code processing..."))
+            app_obj.inform.emit('[success] %s...' % _("Finished G-Code processing"))
 
         if use_thread:
             # To be run in separate thread
@@ -2394,10 +2558,100 @@ class GeometryObject(FlatCAMObj, Geometry):
         else:
             self.app.app_obj.new_object("cncjob", outname, job_init, plot=plot)
 
-    # def on_plot_cb_click(self, *args):
-    #     if self.muted_ui:
-    #         return
-    #     self.read_form_item('plot')
+    def on_polish(self):
+
+        def job_thread(obj):
+            with obj.app.proc_container.new(_("Working ...")):
+                tooldia = obj.ui.polish_dia_entry.get_value()
+                depth = obj.ui.polish_pressure_entry.get_value()
+                travelz = obj.ui.polish_travelz_entry.get_value()
+                margin = obj.ui.polish_margin_entry.get_value()
+                overlap = obj.ui.polish_over_entry.get_value() / 100
+                paint_method = obj.ui.polish_method_combo.get_value()
+
+                # calculate the max uid form the keys of the self.tools
+                max_uid = max(list(obj.tools.keys()))
+                new_uid = max_uid + 1
+
+                # add a new key in the dict
+                new_data = deepcopy(obj.default_data)
+                new_data["travelz"] = travelz
+                new_data["cutz"] = depth
+                new_dict = {
+                    new_uid: {
+                        'tooldia': obj.app.dec_format(float(tooldia), obj.decimals),
+                        'offset': 'Path',
+                        'offset_value': 0.0,
+                        'type': _('Polish'),
+                        'tool_type': 'C1',
+                        'data': new_data,
+                        'solid_geometry': []
+                    }
+                }
+                obj.tools.update(new_dict)
+                obj.sel_tools.update(new_dict)
+
+                # make a box polygon out of the bounds of the current object
+                # apply the margin
+                xmin, ymin, xmax, ymax = obj.bounds()
+                bbox = box(xmin-margin, ymin-margin, xmax+margin, ymax+margin)
+
+                # paint the box
+                try:
+                    # provide the app with a way to process the GUI events when in a blocking loop
+                    QtWidgets.QApplication.processEvents()
+                    if self.app.abort_flag:
+                        # graceful abort requested by the user
+                        raise grace
+
+                    # Type(cpoly) == FlatCAMRTreeStorage | None
+                    cpoly = None
+                    if paint_method == 0:       # Standard
+                        cpoly = self.clear_polygon(bbox,
+                                                   tooldia=tooldia,
+                                                   steps_per_circle=obj.circle_steps,
+                                                   overlap=overlap,
+                                                   contour=True,
+                                                   connect=True,
+                                                   prog_plot=False)
+                    elif paint_method == 1:     # Seed
+                        cpoly = self.clear_polygon2(bbox,
+                                                    tooldia=tooldia,
+                                                    steps_per_circle=obj.circle_steps,
+                                                    overlap=overlap,
+                                                    contour=True,
+                                                    connect=True,
+                                                    prog_plot=False)
+                    elif paint_method == 2:     # Lines
+                        cpoly = self.clear_polygon3(bbox,
+                                                    tooldia=tooldia,
+                                                    steps_per_circle=obj.circle_steps,
+                                                    overlap=overlap,
+                                                    contour=True,
+                                                    connect=True,
+                                                    prog_plot=False)
+
+                    if not cpoly or not cpoly.objects:
+                        obj.app.inform.emit('[ERROR_NOTCL] %s' % _('Geometry could not be painted completely'))
+                        return
+
+                    paint_geo = [g for g in cpoly.get_objects() if g and not g.is_empty]
+                except grace:
+                    return "fail"
+                except Exception as e:
+                    log.debug("Could not Paint the polygons. %s" % str(e))
+                    mssg = '[ERROR] %s\n%s' % (_("Could not do Paint. Try a different combination of parameters. "
+                                                 "Or a different method of Paint"), str(e))
+                    self.app.inform.emit(mssg)
+                    return
+
+                obj.sel_tools[new_uid]['solid_geometry'] = paint_geo
+
+                # and now create the CNCJob
+                obj.launch_job.emit()
+
+        # Send to worker
+        self.app.worker_task.emit({'fcn': job_thread, 'params': [self]})
 
     def scale(self, xfactor, yfactor=None, point=None):
         """
@@ -2488,7 +2742,7 @@ class GeometryObject(FlatCAMObj, Geometry):
             return
 
         self.app.proc_container.new_text = ''
-        self.app.inform.emit('[success] %s' % _("Geometry Scale done."))
+        self.app.inform.emit('[success] %s' % _("Done."))
 
     def offset(self, vect):
         """
@@ -2561,7 +2815,7 @@ class GeometryObject(FlatCAMObj, Geometry):
         self.solid_geometry = translate_recursion(self.solid_geometry)
 
         self.app.proc_container.new_text = ''
-        self.app.inform.emit('[success] %s' % _("Geometry Offset done."))
+        self.app.inform.emit('[success] %s' % _("Done."))
 
     def convert_units(self, units):
         log.debug("FlatCAMObj.GeometryObject.convert_units()")
@@ -2675,7 +2929,7 @@ class GeometryObject(FlatCAMObj, Geometry):
             tooldia = float('%.*f' % (self.decimals, tooldia))
 
             self.ui.addtool_entry.set_value(tooldia)
-        self.ui.addtool_entry.returnPressed.connect(self.on_tool_add)
+        self.ui.addtool_entry.returnPressed.connect(self.on_tool_default_add)
 
         return factor
 
@@ -2874,7 +3128,7 @@ class GeometryObject(FlatCAMObj, Geometry):
         except (ObjectDeleted, AttributeError):
             self.shapes.clear(update=True)
 
-    def on_plot_cb_click(self, *args):
+    def on_plot_cb_click(self):
         if self.muted_ui:
             return
         self.read_form_item('plot')
@@ -2933,7 +3187,7 @@ class GeometryObject(FlatCAMObj, Geometry):
             self.ui.plot_cb.setChecked(True)
         self.ui_connect()
 
-    def on_multicolored_cb_click(self, *args):
+    def on_multicolored_cb_click(self):
         if self.muted_ui:
             return
         self.read_form_item('multicolored')

+ 57 - 54
appObjects/FlatCAMGerber.py

@@ -11,13 +11,11 @@
 # ##########################################################
 
 
-from shapely.geometry import Point, Polygon, MultiPolygon, MultiLineString, LineString, LinearRing
-from shapely.ops import unary_union
+from shapely.geometry import Point, MultiLineString, LineString, LinearRing
 
 from appParsers.ParseGerber import Gerber
 from appObjects.FlatCAMObj import *
 
-import math
 import numpy as np
 from copy import deepcopy
 
@@ -210,9 +208,9 @@ class GerberObject(FlatCAMObj, Gerber):
                 pass
 
             self.apertures_row = 0
-            aper_no = self.apertures_row + 1
+
             sort = []
-            for k, v in list(self.apertures.items()):
+            for k in list(self.apertures.keys()):
                 sort.append(int(k))
             sorted_apertures = sorted(sort)
 
@@ -367,11 +365,11 @@ class GerberObject(FlatCAMObj, Gerber):
         self.app.inform.emit('[WARNING_NOTCL] %s...' % _("Buffering solid geometry"))
 
         def buffer_task():
-            with self.app.proc_container.new('%s...' % _("Buffering")):
+            with self.app.proc_container.new('%s ...' % _("Buffering")):
                 output = self.app.pool.apply_async(self.buffer_handler, args=([self.solid_geometry]))
                 self.solid_geometry = output.get()
 
-                self.app.inform.emit('[success] %s.' % _("Done"))
+                self.app.inform.emit('[success] %s' % _("Done."))
                 self.plot_single_object.emit()
 
         self.app.worker_task.emit({'fcn': buffer_task, 'params': []})
@@ -397,7 +395,7 @@ class GerberObject(FlatCAMObj, Gerber):
             non_copper = bounding_box.difference(self.solid_geometry)
 
             if non_copper is None or non_copper.is_empty:
-                self.app.inform.emit("[ERROR_NOTCL] %s" % _("Operation could not be done."))
+                app_obj.inform.emit("[ERROR_NOTCL] %s" % _("Operation could not be done."))
                 return "fail"
             geo_obj.solid_geometry = non_copper
 
@@ -423,7 +421,7 @@ class GerberObject(FlatCAMObj, Gerber):
                 bounding_box = bounding_box.envelope
 
             if bounding_box is None or bounding_box.is_empty:
-                self.app.inform.emit("[ERROR_NOTCL] %s" % _("Operation could not be done."))
+                app_obj.inform.emit("[ERROR_NOTCL] %s" % _("Operation could not be done."))
                 return "fail"
             geo_obj.solid_geometry = bounding_box
 
@@ -493,7 +491,7 @@ class GerberObject(FlatCAMObj, Gerber):
 
                 geo_obj.solid_geometry = []
 
-                # transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI
+                # transfer the Cut Z and Vtip and Vangle values in case that we use the V-Shape tool in Gerber UI
                 if geo_obj.tool_type.lower() == 'v':
                     new_cutz = self.app.defaults["tools_iso_tool_cutz"]
                     new_vtipdia = self.app.defaults["tools_iso_tool_vtipdia"]
@@ -532,17 +530,16 @@ class GerberObject(FlatCAMObj, Gerber):
                     "startz": self.app.defaults['geometry_startz']
                 })
 
-                geo_obj.tools = {}
-                geo_obj.tools['1'] = {}
+                geo_obj.tools = {'1': {}}
                 geo_obj.tools.update({
                     '1': {
-                        'tooldia': dia,
-                        'offset': 'Path',
-                        'offset_value': 0.0,
-                        'type': _('Rough'),
-                        'tool_type': tool_type,
-                        'data': default_data,
-                        'solid_geometry': geo_obj.solid_geometry
+                        'tooldia':          dia,
+                        'offset':           'Path',
+                        'offset_value':     0.0,
+                        'type':             'Rough',
+                        'tool_type':        tool_type,
+                        'data':             default_data,
+                        'solid_geometry':   geo_obj.solid_geometry
                     }
                 })
 
@@ -555,7 +552,8 @@ class GerberObject(FlatCAMObj, Gerber):
                                                   follow=follow, nr_passes=nr_pass)
 
                     if geom == 'fail':
-                        app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
+                        if plot:
+                            app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
                         return 'fail'
                     geo_obj.solid_geometry.append(geom)
 
@@ -580,7 +578,9 @@ class GerberObject(FlatCAMObj, Gerber):
                 if empty_cnt == len(geo_obj.solid_geometry):
                     raise ValidationError("Empty Geometry", None)
                 else:
-                    app_obj.inform.emit('[success] %s" %s' % (_("Isolation geometry created"), geo_obj.options["name"]))
+                    if plot:
+                        app_obj.inform.emit('[success] %s: %s' %
+                                            (_("Isolation geometry created"), geo_obj.options["name"]))
 
                 # even if combine is checked, one pass is still single-geo
                 geo_obj.multigeo = True if passes > 1 else False
@@ -629,7 +629,8 @@ class GerberObject(FlatCAMObj, Gerber):
                                                   follow=follow, nr_passes=i)
 
                     if geom == 'fail':
-                        app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
+                        if plot:
+                            app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
                         return 'fail'
 
                     geo_obj.solid_geometry = geom
@@ -675,17 +676,16 @@ class GerberObject(FlatCAMObj, Gerber):
                         "startz": self.app.defaults['geometry_startz']
                     })
 
-                    geo_obj.tools = {}
-                    geo_obj.tools['1'] = {}
+                    geo_obj.tools = {'1': {}}
                     geo_obj.tools.update({
                         '1': {
-                            'tooldia': dia,
-                            'offset': 'Path',
-                            'offset_value': 0.0,
-                            'type': _('Rough'),
-                            'tool_type': tool_type,
-                            'data': default_data,
-                            'solid_geometry': geo_obj.solid_geometry
+                            'tooldia':          dia,
+                            'offset':           'Path',
+                            'offset_value':     0.0,
+                            'type':             'Rough',
+                            'tool_type':        tool_type,
+                            'data':             default_data,
+                            'solid_geometry':   geo_obj.solid_geometry
                         }
                     })
 
@@ -706,8 +706,9 @@ class GerberObject(FlatCAMObj, Gerber):
                     if empty_cnt == len(geo_obj.solid_geometry):
                         raise ValidationError("Empty Geometry", None)
                     else:
-                        app_obj.inform.emit('[success] %s: %s' %
-                                            (_("Isolation geometry created"), geo_obj.options["name"]))
+                        if plot:
+                            app_obj.inform.emit('[success] %s: %s' %
+                                                (_("Isolation geometry created"), geo_obj.options["name"]))
                     geo_obj.multigeo = False
 
                     # ############################################################
@@ -771,7 +772,7 @@ class GerberObject(FlatCAMObj, Gerber):
         else:
             follow_name = outname
 
-        def follow_init(follow_obj, app):
+        def follow_init(follow_obj, app_obj):
             # Propagate options
             follow_obj.options["cnctooldia"] = str(self.app.defaults["tools_iso_tooldia"])
             follow_obj.solid_geometry = self.follow_geometry
@@ -849,7 +850,7 @@ class GerberObject(FlatCAMObj, Gerber):
 
         log.debug("FlatCAMObj.GerberObject.convert_units()")
 
-        factor = Gerber.convert_units(self, units)
+        Gerber.convert_units(self, units)
 
         # self.options['isotooldia'] = float(self.options['isotooldia']) * factor
         # self.options['bboxmargin'] = float(self.options['bboxmargin']) * factor
@@ -956,11 +957,12 @@ class GerberObject(FlatCAMObj, Gerber):
             log.debug("GerberObject.plot() --> %s" % str(e))
 
     # experimental plot() when the solid_geometry is stored in the self.apertures
-    def plot_aperture(self, run_thread=False, **kwargs):
+    def plot_aperture(self, only_flashes=False, run_thread=False, **kwargs):
         """
 
-        :param run_thread: if True run the aperture plot as a thread in a worker
-        :param kwargs: color and face_color
+        :param only_flashes:    plot only flashed
+        :param run_thread:      if True run the aperture plot as a thread in a worker
+        :param kwargs:          color and face_color
         :return:
         """
 
@@ -989,35 +991,36 @@ class GerberObject(FlatCAMObj, Gerber):
         else:
             visibility = kwargs['visible']
 
-        with self.app.proc_container.new(_("Plotting Apertures")):
-
-            def job_thread(app_obj):
+        def job_thread(app_obj):
+            with self.app.proc_container.new('%s ...' % _("Plotting")):
                 try:
                     if aperture_to_plot_mark in self.apertures:
-                        for elem in self.apertures[aperture_to_plot_mark]['geometry']:
+                        for elem in app_obj.apertures[aperture_to_plot_mark]['geometry']:
                             if 'solid' in elem:
+                                if only_flashes and not isinstance(elem['follow'], Point):
+                                    continue
                                 geo = elem['solid']
                                 try:
                                     for el in geo:
-                                        shape_key = self.add_mark_shape(shape=el, color=color, face_color=color,
-                                                                        visible=visibility)
-                                        self.mark_shapes_storage[aperture_to_plot_mark].append(shape_key)
+                                        shape_key = app_obj.add_mark_shape(shape=el, color=color, face_color=color,
+                                                                           visible=visibility)
+                                        app_obj.mark_shapes_storage[aperture_to_plot_mark].append(shape_key)
                                 except TypeError:
-                                    shape_key = self.add_mark_shape(shape=geo, color=color, face_color=color,
-                                                                    visible=visibility)
-                                    self.mark_shapes_storage[aperture_to_plot_mark].append(shape_key)
+                                    shape_key = app_obj.add_mark_shape(shape=geo, color=color, face_color=color,
+                                                                       visible=visibility)
+                                    app_obj.mark_shapes_storage[aperture_to_plot_mark].append(shape_key)
 
-                    self.mark_shapes.redraw()
+                    app_obj.mark_shapes.redraw()
 
                 except (ObjectDeleted, AttributeError):
-                    self.clear_plot_apertures()
+                    app_obj.clear_plot_apertures()
                 except Exception as e:
                     log.debug("GerberObject.plot_aperture() --> %s" % str(e))
 
-            if run_thread:
-                self.app.worker_task.emit({'fcn': job_thread, 'params': [self]})
-            else:
-                job_thread(self)
+        if run_thread:
+            self.app.worker_task.emit({'fcn': job_thread, 'params': [self]})
+        else:
+            job_thread(self)
 
     def clear_plot_apertures(self, aperture='all'):
         """

+ 4 - 4
appObjects/FlatCAMObj.py

@@ -263,7 +263,7 @@ class FlatCAMObj(QtCore.QObject):
             with self.app.proc_container.new(_("Offsetting...")):
                 self.offset(vector_val)
             self.app.proc_container.update_view_text('')
-            with self.app.proc_container.new('%s...' % _("Plotting")):
+            with self.app.proc_container.new('%s ...' % _("Plotting")):
                 self.plot()
             self.app.app_obj.object_changed.emit(self)
 
@@ -294,7 +294,7 @@ class FlatCAMObj(QtCore.QObject):
                 self.app.inform.emit('[success] %s' % _("Scale done."))
 
             self.app.proc_container.update_view_text('')
-            with self.app.proc_container.new('%s...' % _("Plotting")):
+            with self.app.proc_container.new('%s ...' % _("Plotting")):
                 self.plot()
             self.app.app_obj.object_changed.emit(self)
 
@@ -310,7 +310,7 @@ class FlatCAMObj(QtCore.QObject):
             with self.app.proc_container.new(_("Skewing...")):
                 self.skew(x_angle, y_angle)
             self.app.proc_container.update_view_text('')
-            with self.app.proc_container.new('%s...' % _("Plotting")):
+            with self.app.proc_container.new('%s ...' % _("Plotting")):
                 self.plot()
             self.app.app_obj.object_changed.emit(self)
 
@@ -390,7 +390,7 @@ class FlatCAMObj(QtCore.QObject):
 
     def single_object_plot(self):
         def plot_task():
-            with self.app.proc_container.new('%s...' % _("Plotting")):
+            with self.app.proc_container.new('%s ...' % _("Plotting")):
                 self.plot()
             self.app.app_obj.object_changed.emit(self)
 

+ 4 - 9
appObjects/FlatCAMScript.py

@@ -16,7 +16,6 @@ from appGUI.ObjectUI import *
 
 import tkinter as tk
 import sys
-from copy import deepcopy
 
 import gettext
 import appTranslation as fcTranslate
@@ -78,13 +77,9 @@ class ScriptObject(FlatCAMObj):
 
         # Show/Hide Advanced Options
         if self.app.defaults["global_app_level"] == 'b':
-            self.ui.level.setText(_(
-                '<span style="color:green;"><b>Basic</b></span>'
-            ))
+            self.ui.level.setText('<span style="color:green;"><b>%s</b></span>' % _("Basic"))
         else:
-            self.ui.level.setText(_(
-                '<span style="color:red;"><b>Advanced</b></span>'
-            ))
+            self.ui.level.setText('<span style="color:red;"><b>%s</b></span>' % _("Advanced"))
 
         self.script_editor_tab = AppTextEditor(app=self.app, plain_text=True, parent=self.app.ui)
 
@@ -131,7 +126,7 @@ class ScriptObject(FlatCAMObj):
         # ---------------------------------------------------- #
         # ----------- LOAD THE TEXT SOURCE FILE -------------- #
         # ---------------------------------------------------- #
-        self.app.proc_container.view.set_busy(_("Loading..."))
+        self.app.proc_container.view.set_busy('%s...' % _("Loading"))
         self.script_editor_tab.t_frame.hide()
 
         try:
@@ -219,7 +214,7 @@ class ScriptObject(FlatCAMObj):
             # it means that the script finished with an error
             result = self.app.shell.tcl.eval("set errorInfo")
             log.error("Exec command Exception: %s\n" % result)
-            self.app.shell.append_error('ERROR: %s\n '% result)
+            self.app.shell.append_error('ERROR: %s\n' % result)
 
         self.app.ui.fcinfo.lock_pmaps = False
         self.app.shell.close_processing()

+ 12 - 12
appObjects/ObjectCollection.py

@@ -229,12 +229,12 @@ class ObjectCollection(QtCore.QAbstractItemModel):
     """
 
     groups = [
-        ("gerber", "Gerber"),
-        ("excellon", "Excellon"),
-        ("geometry", "Geometry"),
+        ("gerber", _("Gerber")),
+        ("excellon", _("Excellon")),
+        ("geometry", _("Geometry")),
         ("cncjob", "CNC Job"),
-        ("script", "Scripts"),
-        ("document", "Document"),
+        ("script", _("Script")),
+        ("document", _("Document")),
     ]
 
     classdict = {
@@ -1090,14 +1090,14 @@ class ObjectCollection(QtCore.QAbstractItemModel):
 
             def add_act(o_name):
                 obj_for_icon = self.get_by_name(o_name)
-                add_action = QtWidgets.QAction(parent=self.app.ui.menuobjects)
-                add_action.setCheckable(True)
-                add_action.setText(o_name)
-                add_action.setIcon(QtGui.QIcon(icon_files[obj_for_icon.kind]))
-                add_action.triggered.connect(
-                    lambda: self.set_active(o_name) if add_action.isChecked() is True else
+                menu_action = QtWidgets.QAction(parent=self.app.ui.menuobjects)
+                menu_action.setCheckable(True)
+                menu_action.setText(o_name)
+                menu_action.setIcon(QtGui.QIcon(icon_files[obj_for_icon.kind]))
+                menu_action.triggered.connect(
+                    lambda: self.set_active(o_name) if menu_action.isChecked() is True else
                     self.set_inactive(o_name))
-                self.app.ui.menuobjects.addAction(add_action)
+                self.app.ui.menuobjects.addAction(menu_action)
 
             for name in gerber_list:
                 add_act(name)

+ 10 - 10
appParsers/ParseDXF.py

@@ -9,13 +9,12 @@ from shapely.geometry import LineString
 from shapely.affinity import rotate
 from ezdxf.math.vector import Vector as ezdxf_vector
 
+from appParsers.ParseFont import *
+from appParsers.ParseDXF_Spline import *
 import logging
 
 log = logging.getLogger('base2')
 
-from appParsers.ParseFont import *
-from appParsers.ParseDXF_Spline import *
-
 
 def distance(pt1, pt2):
     return math.sqrt((pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2)
@@ -107,12 +106,12 @@ def dxfarc2shapely(arc, n_points=100):
         arc_center = ocs.to_wcs(arc.dxf.center)
         start_angle = arc.dxf.start_angle + 180
         end_angle = arc.dxf.end_angle + 180
-        dir = 'CW'
+        direction = 'CW'
     else:
         arc_center = arc.dxf.center
         start_angle = arc.dxf.start_angle
         end_angle = arc.dxf.end_angle
-        dir = 'CCW'
+        direction = 'CCW'
 
     center_x = arc_center[0]
     center_y = arc_center[1]
@@ -127,7 +126,7 @@ def dxfarc2shapely(arc, n_points=100):
     step_angle = float(abs(end_angle - start_angle) / n_points)
 
     while angle <= end_angle:
-        if dir == 'CCW':
+        if direction == 'CCW':
             x = center_x + radius * math.cos(math.radians(angle))
             y = center_y + radius * math.sin(math.radians(angle))
         else:
@@ -138,7 +137,7 @@ def dxfarc2shapely(arc, n_points=100):
 
     # in case the number of segments do not cover everything until the end of the arc
     if angle != end_angle:
-        if dir == 'CCW':
+        if direction == 'CCW':
             x = center_x + radius * math.cos(math.radians(end_angle))
             y = center_y + radius * math.sin(math.radians(end_angle))
         else:
@@ -164,12 +163,12 @@ def dxfellipse2shapely(ellipse, ellipse_segments=100):
         center = ocs.to_wcs(ellipse.dxf.center)
         start_angle = ocs.to_wcs(ellipse.dxf.start_param)
         end_angle = ocs.to_wcs(ellipse.dxf.end_param)
-        dir = 'CW'
+        direction = 'CW'
     else:
         center = ellipse.dxf.center
         start_angle = ellipse.dxf.start_param
         end_angle = ellipse.dxf.end_param
-        dir = 'CCW'
+        direction = 'CCW'
 
     # print("Dir = %s" % dir)
     major_axis = ellipse.dxf.major_axis
@@ -189,7 +188,7 @@ def dxfellipse2shapely(ellipse, ellipse_segments=100):
 
     angle = start_angle
     for step in range(line_seg + 1):
-        if dir == 'CW':
+        if direction == 'CW':
             major_dim = normalize_2(major_axis)
             minor_dim = normalize_2(Vector([ratio * k for k in major_axis]))
             vx = (major_dim[0] + major_dim[1]) * math.cos(angle)
@@ -381,6 +380,7 @@ def get_geo(dxf_object, container):
 
     return geo
 
+
 def getdxftext(exf_object, object_type, units=None):
     pass
 

+ 1 - 2
appParsers/ParseDXF_Spline.py

@@ -400,7 +400,7 @@ class Vector(list):
 
     # ----------------------------------------------------------------------
     def __str__(self):
-        return "[%s]" % ", ".join([("%15g" % (x)).strip() for x in self])
+        return "[%s]" % ", ".join([("%15g" % x).strip() for x in self])
 
     # ----------------------------------------------------------------------
     def eq(self, v, acc=_accuracy):
@@ -805,7 +805,6 @@ class Vector(list):
 #
 #     #-----------------------------------------------------------------------
 #     def __call__(self, i, x):
-#         # FIXME should interpolate to find the interval
 #         C = self.coefficients(i)
 #         return ((C[0]*x + C[1])*x + C[2])*x + C[3]
 #

+ 16 - 14
appParsers/ParseFont.py

@@ -10,13 +10,15 @@
 # ## and made it work with Python 3                                    #
 # ######################################################################
 
-import re, os, sys, glob
+import re
+import os
+import sys
+import glob
 
-from shapely.geometry import Point, Polygon
+from shapely.geometry import Polygon
 from shapely.affinity import translate, scale
 from shapely.geometry import MultiPolygon
 
-
 import freetype as ft
 from fontTools import ttLib
 
@@ -32,7 +34,7 @@ if '_' not in builtins.__dict__:
 log = logging.getLogger('base2')
 
 
-class ParseFont():
+class ParseFont:
 
     FONT_SPECIFIER_NAME_ID = 4
     FONT_SPECIFIER_FAMILY_ID = 1
@@ -69,12 +71,12 @@ class ParseFont():
         if os.path.isfile(executable):
             data = os.popen(executable).readlines()
             match = re.compile('\d+: (.+)')
-            set = []
+            set_lst = []
             for line in data:
                 result = match.match(line)
                 if result:
-                    set.append(result.group(1))
-            return set
+                    set_lst.append(result.group(1))
+            return set_lst
         else:
             directories = [
                 # what seems to be the standard installation point
@@ -91,7 +93,7 @@ class ParseFont():
             dir_set = []
 
             for directory in directories:
-                directory = directory = os.path.expanduser(os.path.expandvars(directory))
+                directory = os.path.expanduser(os.path.expandvars(directory))
                 try:
                     if os.path.isdir(directory):
                         for path, children, files in os.walk(directory):
@@ -116,7 +118,7 @@ class ParseFont():
         dir_set = []
 
         for directory in directories:
-            directory = directory = os.path.expanduser(os.path.expandvars(directory))
+            directory = os.path.expanduser(os.path.expandvars(directory))
             try:
                 if os.path.isdir(directory):
                     for path, children, files in os.walk(directory):
@@ -144,7 +146,7 @@ class ParseFont():
                     winreg.HKEY_LOCAL_MACHINE,
                     keyName
                 )
-            except OSError as err:
+            except OSError:
                 pass
 
         if not k:
@@ -195,7 +197,7 @@ class ParseFont():
                 break
         return name, family
 
-    def __init__(self, app, parent=None):
+    def __init__(self, app):
         super(ParseFont, self).__init__()
 
         self.app = app
@@ -217,7 +219,7 @@ class ParseFont():
         if paths is None:
             if sys.platform == 'win32':
                 font_directory = ParseFont.get_win32_font_path()
-                paths = [font_directory,]
+                paths = [font_directory, ]
 
                 # now get all installed fonts directly...
                 for f in self.get_win32_fonts(font_directory):
@@ -275,7 +277,7 @@ class ParseFont():
             else:
                 try:
                     name = name.replace(" Regular", '')
-                except Exception as e:
+                except Exception:
                     pass
                 self.regular_f.update({name: font})
         log.debug("Font parsing is finished.")
@@ -318,7 +320,7 @@ class ParseFont():
                 if previous > 0 and glyph_index > 0:
                     delta = face.get_kerning(previous, glyph_index)
                     pen_x += delta.x
-            except Exception as e:
+            except Exception:
                 pass
 
             face.load_glyph(glyph_index)

+ 61 - 64
appParsers/ParseGerber.py

@@ -2,16 +2,13 @@ from PyQt5 import QtWidgets
 from camlib import Geometry, arc, arc_angle, ApertureMacro, grace
 
 import numpy as np
-# import re
-# import logging
 import traceback
 from copy import deepcopy
-# import sys
 
 from shapely.ops import unary_union, linemerge
-# from shapely.affinity import scale, translate
 import shapely.affinity as affinity
 from shapely.geometry import box as shply_box
+from shapely.geometry import Point
 
 from lxml import etree as ET
 import ezdxf
@@ -296,6 +293,7 @@ class Gerber(Geometry):
 
         if apertureType in self.aperture_macros:
             self.apertures[apid] = {"type": "AM",
+                                    # "size": 0.0,
                                     "macro": self.aperture_macros[apertureType],
                                     "modifiers": paramList}
             return apid
@@ -315,12 +313,12 @@ class Gerber(Geometry):
 
         First is ``G54D11*`` and seconds is ``G36*``.
 
-        :param filename: Gerber file to parse.
-        :type filename: str
-        :param follow: If true, will not create polygons, just lines
-            following the gerber path.
-        :type follow: bool
-        :return: None
+        :param filename:        Gerber file to parse.
+        :type filename:         str
+        :param follow:          If true, will not create polygons, just lines
+                                following the gerber path.
+        :type follow:           bool
+        :return:                None
         """
 
         with open(filename, 'r') as gfile:
@@ -423,7 +421,7 @@ class Gerber(Geometry):
 
         s_tol = float(self.app.defaults["gerber_simp_tolerance"])
 
-        self.app.inform.emit('%s %d %s.' % (_("Gerber processing. Parsing"), len(glines), _("lines")))
+        self.app.inform.emit('%s %d %s.' % (_("Gerber processing. Parsing"), len(glines), _("Lines").lower()))
         try:
             for gline in glines:
                 if self.app.abort_flag:
@@ -955,36 +953,41 @@ class Gerber(Geometry):
                                 # Reset path starting point
                                 path = [[current_x, current_y]]
 
-                                # --- BUFFERED ---
-                                # Draw the flash
-                                # this treats the case when we are storing geometry as paths
-                                geo_dict = {}
-                                geo_flash = Point([current_x, current_y])
-                                follow_buffer.append(geo_flash)
-                                geo_dict['follow'] = geo_flash
-
-                                # this treats the case when we are storing geometry as solids
-                                flash = self.create_flash_geometry(
-                                    Point([current_x, current_y]),
-                                    self.apertures[current_aperture],
-                                    self.steps_per_circle
-                                )
-                                if not flash.is_empty:
-                                    if self.app.defaults['gerber_simplification']:
-                                        poly_buffer.append(flash.simplify(s_tol))
-                                    else:
-                                        poly_buffer.append(flash)
+                                # treat the case when there is a flash inside a Gerber Region when the current_aperture
+                                # is None
+                                if current_aperture is None:
+                                    pass
+                                else:
+                                    # --- BUFFERED ---
+                                    # Draw the flash
+                                    # this treats the case when we are storing geometry as paths
+                                    geo_dict = {}
+                                    geo_flash = Point([current_x, current_y])
+                                    follow_buffer.append(geo_flash)
+                                    geo_dict['follow'] = geo_flash
+
+                                    # this treats the case when we are storing geometry as solids
+                                    flash = self.create_flash_geometry(
+                                        Point([current_x, current_y]),
+                                        self.apertures[current_aperture],
+                                        self.steps_per_circle
+                                    )
+                                    if not flash.is_empty:
+                                        if self.app.defaults['gerber_simplification']:
+                                            poly_buffer.append(flash.simplify(s_tol))
+                                        else:
+                                            poly_buffer.append(flash)
 
-                                    if self.is_lpc is True:
-                                        geo_dict['clear'] = flash
-                                    else:
-                                        geo_dict['solid'] = flash
+                                        if self.is_lpc is True:
+                                            geo_dict['clear'] = flash
+                                        else:
+                                            geo_dict['solid'] = flash
 
-                                if current_aperture not in self.apertures:
-                                    self.apertures[current_aperture] = {}
-                                if 'geometry' not in self.apertures[current_aperture]:
-                                    self.apertures[current_aperture]['geometry'] = []
-                                self.apertures[current_aperture]['geometry'].append(deepcopy(geo_dict))
+                                    if current_aperture not in self.apertures:
+                                        self.apertures[current_aperture] = {}
+                                    if 'geometry' not in self.apertures[current_aperture]:
+                                        self.apertures[current_aperture]['geometry'] = []
+                                    self.apertures[current_aperture]['geometry'].append(deepcopy(geo_dict))
 
                             if making_region is False:
                                 # if the aperture is rectangle then add a rectangular shape having as parameters the
@@ -1837,12 +1840,10 @@ class Gerber(Geometry):
                 geos_length = 1
 
             if geos_length == 1:
-                geo_qrcode = []
-                geo_qrcode.append(Polygon(geos[0].exterior))
+                geo_qrcode = [Polygon(geos[0].exterior)]
                 for i_el in geos[0].interiors:
                     geo_qrcode.append(Polygon(i_el).buffer(0, resolution=res))
-                for poly in geo_qrcode:
-                    geos.append(poly)
+                geos = [poly for poly in geo_qrcode]
 
             if type(self.solid_geometry) == list:
                 self.solid_geometry += geos
@@ -1864,15 +1865,14 @@ class Gerber(Geometry):
             self.solid_geometry = [self.solid_geometry]
 
         if '0' not in self.apertures:
-            self.apertures['0'] = {}
-            self.apertures['0']['type'] = 'REG'
-            self.apertures['0']['size'] = 0.0
-            self.apertures['0']['geometry'] = []
+            self.apertures['0'] = {
+                'type': 'REG',
+                'size': 0.0,
+                'geometry': []
+            }
 
         for pol in self.solid_geometry:
-            new_el = {}
-            new_el['solid'] = pol
-            new_el['follow'] = pol.exterior
+            new_el = {'solid': pol, 'follow': pol.exterior}
             self.apertures['0']['geometry'].append(new_el)
 
     def import_dxf_as_gerber(self, filename, units='MM'):
@@ -1914,15 +1914,14 @@ class Gerber(Geometry):
 
         # create the self.apertures data structure
         if '0' not in self.apertures:
-            self.apertures['0'] = {}
-            self.apertures['0']['type'] = 'REG'
-            self.apertures['0']['size'] = 0.0
-            self.apertures['0']['geometry'] = []
+            self.apertures['0'] = {
+                'type': 'REG',
+                'size': 0.0,
+                'geometry': []
+            }
 
         for pol in flat_geo:
-            new_el = {}
-            new_el['solid'] = pol
-            new_el['follow'] = pol
+            new_el = {'solid': pol, 'follow': pol}
             self.apertures['0']['geometry'].append(deepcopy(new_el))
 
     def scale(self, xfactor, yfactor=None, point=None):
@@ -2043,7 +2042,7 @@ class Gerber(Geometry):
             log.debug('camlib.Gerber.scale() Exception --> %s' % str(e))
             return 'fail'
 
-        self.app.inform.emit('[success] %s' % _("Gerber Scale done."))
+        self.app.inform.emit('[success] %s' % _("Done."))
         self.app.proc_container.new_text = ''
 
         # ## solid_geometry ???
@@ -2134,8 +2133,7 @@ class Gerber(Geometry):
             log.debug('camlib.Gerber.offset() Exception --> %s' % str(e))
             return 'fail'
 
-        self.app.inform.emit('[success] %s' %
-                             _("Gerber Offset done."))
+        self.app.inform.emit('[success] %s' % _("Done."))
         self.app.proc_container.new_text = ''
 
     def mirror(self, axis, point):
@@ -2210,8 +2208,7 @@ class Gerber(Geometry):
             log.debug('camlib.Gerber.mirror() Exception --> %s' % str(e))
             return 'fail'
 
-        self.app.inform.emit('[success] %s' %
-                             _("Gerber Mirror done."))
+        self.app.inform.emit('[success] %s' % _("Done."))
         self.app.proc_container.new_text = ''
 
     def skew(self, angle_x, angle_y, point):
@@ -2285,7 +2282,7 @@ class Gerber(Geometry):
             log.debug('camlib.Gerber.skew() Exception --> %s' % str(e))
             return 'fail'
 
-        self.app.inform.emit('[success] %s' % _("Gerber Skew done."))
+        self.app.inform.emit('[success] %s' % _("Done."))
         self.app.proc_container.new_text = ''
 
     def rotate(self, angle, point):
@@ -2347,7 +2344,7 @@ class Gerber(Geometry):
         except Exception as e:
             log.debug('camlib.Gerber.rotate() Exception --> %s' % str(e))
             return 'fail'
-        self.app.inform.emit('[success] %s' % _("Gerber Rotate done."))
+        self.app.inform.emit('[success] %s' % _("Done."))
         self.app.proc_container.new_text = ''
 
     def buffer(self, distance, join=2, factor=None):

+ 1 - 1
appParsers/ParseHPGL2.py

@@ -198,7 +198,7 @@ class HPGL2:
         line_num = 0
         gline = ""
 
-        self.app.inform.emit('%s %d %s.' % (_("HPGL2 processing. Parsing"), len(glines), _("lines")))
+        self.app.inform.emit('%s %d %s.' % (_("HPGL2 processing. Parsing"), len(glines), _("Lines").lower()))
         try:
             for gline in glines:
                 if self.app.abort_flag:

+ 71 - 112
appParsers/ParsePDF.py

@@ -82,12 +82,10 @@ class PdfParser(QtCore.QObject):
         self.restore_gs_re = re.compile(r'^.*Q.*$')
 
         # graphic stack where we save parameters like transformation, line_width
-        self.gs = {}
         # each element is a list composed of sublist elements
         # (each sublist has 2 lists each having 2 elements: first is offset like:
         # offset_geo = [off_x, off_y], second element is scale list with 2 elements, like: scale_geo = [sc_x, sc_yy])
-        self.gs['transform'] = []
-        self.gs['line_width'] = []  # each element is a float
+        self.gs = {'transform': [], 'line_width': []}
 
         # conversion factor to INCH
         self.point_to_unit_factor = 0.01388888888
@@ -102,15 +100,17 @@ class PdfParser(QtCore.QObject):
             # 1 inch = 72 points => 1 point = 1 / 72 = 0.01388888888 inch
             self.point_to_unit_factor = 1 / 72
 
-        path = {}
-        path['lines'] = []  # it's a list of lines subpaths
-        path['bezier'] = []  # it's a list of bezier arcs subpaths
-        path['rectangle'] = []  # it's a list of rectangle subpaths
+        path = {
+            'lines': [],        # it's a list of lines subpaths
+            'bezier': [],       # it's a list of bezier arcs subpaths
+            'rectangle': []     # it's a list of rectangle subpaths
+        }
 
-        subpath = {}
-        subpath['lines'] = []  # it's a list of points
-        subpath['bezier'] = []  # it's a list of sublists each like this [start, c1, c2, stop]
-        subpath['rectangle'] = []  # it's a list of sublists of points
+        subpath = {
+            'lines': [],        # it's a list of points
+            'bezier': [],       # it's a list of sublists each like this [start, c1, c2, stop]
+            'rectangle': []     # it's a list of sublists of points
+        }
 
         # store the start point (when 'm' command is encountered)
         current_subpath = None
@@ -141,12 +141,14 @@ class PdfParser(QtCore.QObject):
 
         # store the apertures with clear geometry here
         # we are interested only in the circular geometry (drill holes) therefore we target only Bezier subpaths
-        clear_apertures_dict = {}
         # everything will be stored in the '0' aperture since we are dealing with clear polygons not strokes
-        clear_apertures_dict['0'] = {}
-        clear_apertures_dict['0']['size'] = 0.0
-        clear_apertures_dict['0']['type'] = 'C'
-        clear_apertures_dict['0']['geometry'] = []
+        clear_apertures_dict = {
+            '0': {
+                'size': 0.0,
+                'type': 'C',
+                'geometry': []
+            }
+        }
 
         # on stroke color change we create a new apertures dictionary and store the old one in a storage from where
         # it will be transformed into Gerber object
@@ -527,14 +529,10 @@ class PdfParser(QtCore.QObject):
                         for pdf_geo in path_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in pdf_geo:
-                                    new_el = {}
-                                    new_el['solid'] = poly
-                                    new_el['follow'] = poly.exterior
+                                    new_el = {'solid': poly, 'follow': poly.exterior}
                                     apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = {}
-                                new_el['solid'] = pdf_geo
-                                new_el['follow'] = pdf_geo.exterior
+                                new_el = {'solid': pdf_geo, 'follow': pdf_geo.exterior}
                                 apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el))
                     else:
                         if str(aperture) in apertures_dict.keys():
@@ -546,14 +544,10 @@ class PdfParser(QtCore.QObject):
                         for pdf_geo in path_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in pdf_geo:
-                                    new_el = {}
-                                    new_el['solid'] = poly
-                                    new_el['follow'] = poly.exterior
+                                    new_el = {'solid': poly, 'follow': poly.exterior}
                                     apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = {}
-                                new_el['solid'] = pdf_geo
-                                new_el['follow'] = pdf_geo.exterior
+                                new_el = {'solid': pdf_geo, 'follow': pdf_geo.exterior}
                                 apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
                 else:
                     apertures_dict[str(aperture)] = {}
@@ -563,14 +557,10 @@ class PdfParser(QtCore.QObject):
                     for pdf_geo in path_geo:
                         if isinstance(pdf_geo, MultiPolygon):
                             for poly in pdf_geo:
-                                new_el = {}
-                                new_el['solid'] = poly
-                                new_el['follow'] = poly.exterior
+                                new_el = {'solid': poly, 'follow': poly.exterior}
                                 apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
                         else:
-                            new_el = {}
-                            new_el['solid'] = pdf_geo
-                            new_el['follow'] = pdf_geo.exterior
+                            new_el = {'solid': pdf_geo, 'follow': pdf_geo.exterior}
                             apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
 
                 continue
@@ -675,12 +665,10 @@ class PdfParser(QtCore.QObject):
                         if path_geo:
                             try:
                                 for g in path_geo:
-                                    new_el = {}
-                                    new_el['clear'] = g
+                                    new_el = {'clear': g}
                                     clear_apertures_dict['0']['geometry'].append(new_el)
                             except TypeError:
-                                new_el = {}
-                                new_el['clear'] = path_geo
+                                new_el = {'clear': path_geo}
                                 clear_apertures_dict['0']['geometry'].append(new_el)
 
                     # now that we finished searching for drill holes (this is not very precise because holes in the
@@ -690,12 +678,10 @@ class PdfParser(QtCore.QObject):
                         for pdf_geo in path_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in pdf_geo:
-                                    new_el = {}
-                                    new_el['clear'] = poly
+                                    new_el = {'clear': poly}
                                     apertures_dict['0']['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = {}
-                                new_el['clear'] = pdf_geo
+                                new_el = {'clear': pdf_geo}
                                 apertures_dict['0']['geometry'].append(deepcopy(new_el))
                     except KeyError:
                         # in case there is no stroke width yet therefore no aperture
@@ -706,12 +692,10 @@ class PdfParser(QtCore.QObject):
                         for pdf_geo in path_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in pdf_geo:
-                                    new_el = {}
-                                    new_el['clear'] = poly
+                                    new_el = {'clear': poly}
                                     apertures_dict['0']['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = {}
-                                new_el['clear'] = pdf_geo
+                                new_el = {'clear': pdf_geo}
                                 apertures_dict['0']['geometry'].append(deepcopy(new_el))
                 else:
                     # else, add the geometry as usual
@@ -719,14 +703,10 @@ class PdfParser(QtCore.QObject):
                         for pdf_geo in path_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in pdf_geo:
-                                    new_el = {}
-                                    new_el['solid'] = poly
-                                    new_el['follow'] = poly.exterior
+                                    new_el = {'solid': poly, 'follow': poly.exterior}
                                     apertures_dict['0']['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = {}
-                                new_el['solid'] = pdf_geo
-                                new_el['follow'] = pdf_geo.exterior
+                                new_el = {'solid': pdf_geo, 'follow': pdf_geo.exterior}
                                 apertures_dict['0']['geometry'].append(deepcopy(new_el))
                     except KeyError:
                         # in case there is no stroke width yet therefore no aperture
@@ -737,14 +717,10 @@ class PdfParser(QtCore.QObject):
                         for pdf_geo in path_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in pdf_geo:
-                                    new_el = {}
-                                    new_el['solid'] = poly
-                                    new_el['follow'] = poly.exterior
+                                    new_el = {'solid': poly, 'follow': poly.exterior}
                                     apertures_dict['0']['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = {}
-                                new_el['solid'] = pdf_geo
-                                new_el['follow'] = pdf_geo.exterior
+                                new_el = {'solid': pdf_geo, 'follow': pdf_geo.exterior}
                                 apertures_dict['0']['geometry'].append(deepcopy(new_el))
                     continue
 
@@ -890,50 +866,41 @@ class PdfParser(QtCore.QObject):
                         for pdf_geo in path_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in pdf_geo:
-                                    new_el = {}
-                                    new_el['solid'] = poly
-                                    new_el['follow'] = poly.exterior
+                                    new_el = {'solid': poly, 'follow': poly.exterior}
                                     apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = {}
-                                new_el['solid'] = pdf_geo
-                                new_el['follow'] = pdf_geo.exterior
+                                new_el = {'solid': pdf_geo, 'follow': pdf_geo.exterior}
                                 apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el))
                     else:
                         if str(aperture) in apertures_dict.keys():
                             aperture += 1
-                        apertures_dict[str(aperture)] = {}
-                        apertures_dict[str(aperture)]['size'] = round(applied_size, 5)
-                        apertures_dict[str(aperture)]['type'] = 'C'
-                        apertures_dict[str(aperture)]['geometry'] = []
+                        apertures_dict[str(aperture)] = {
+                            'size': round(applied_size, 5),
+                            'type': 'C',
+                            'geometry': []
+                        }
                         for pdf_geo in path_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in pdf_geo:
-                                    new_el = {}
-                                    new_el['solid'] = poly
-                                    new_el['follow'] = poly.exterior
+                                    new_el = {'solid': poly, 'follow': poly.exterior}
                                     apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = {}
-                                new_el['solid'] = pdf_geo
-                                new_el['follow'] = pdf_geo.exterior
+                                new_el = {'solid': pdf_geo, 'follow': pdf_geo.exterior}
                                 apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
                 else:
-                    apertures_dict[str(aperture)] = {}
-                    apertures_dict[str(aperture)]['size'] = round(applied_size, 5)
-                    apertures_dict[str(aperture)]['type'] = 'C'
-                    apertures_dict[str(aperture)]['geometry'] = []
+                    apertures_dict[str(aperture)] = {
+                        'size': round(applied_size, 5),
+                        'type': 'C',
+                        'geometry': []
+                    }
+
                     for pdf_geo in path_geo:
                         if isinstance(pdf_geo, MultiPolygon):
                             for poly in pdf_geo:
-                                new_el = {}
-                                new_el['solid'] = poly
-                                new_el['follow'] = poly.exterior
+                                new_el = {'solid': poly, 'follow': poly.exterior}
                                 apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
                         else:
-                            new_el = {}
-                            new_el['solid'] = pdf_geo
-                            new_el['follow'] = pdf_geo.exterior
+                            new_el = {'solid': pdf_geo, 'follow': pdf_geo.exterior}
                             apertures_dict[str(aperture)]['geometry'].append(deepcopy(new_el))
 
                 # ############################################# ##
@@ -946,60 +913,52 @@ class PdfParser(QtCore.QObject):
                         for pdf_geo in path_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in fill_geo:
-                                    new_el = {}
-                                    new_el['clear'] = poly
+                                    new_el = {'clear': poly}
                                     apertures_dict['0']['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = {}
-                                new_el['clear'] = pdf_geo
+                                new_el = {'clear': pdf_geo}
                                 apertures_dict['0']['geometry'].append(deepcopy(new_el))
                     except KeyError:
                         # in case there is no stroke width yet therefore no aperture
-                        apertures_dict['0'] = {}
-                        apertures_dict['0']['size'] = round(applied_size, 5)
-                        apertures_dict['0']['type'] = 'C'
-                        apertures_dict['0']['geometry'] = []
+                        apertures_dict['0'] = {
+                            'size': round(applied_size, 5),
+                            'type': 'C',
+                            'geometry': []
+                        }
+
                         for pdf_geo in fill_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in pdf_geo:
-                                    new_el = {}
-                                    new_el['clear'] = poly
+                                    new_el = {'clear': poly}
                                     apertures_dict['0']['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = {}
-                                new_el['clear'] = pdf_geo
+                                new_el = {'clear': pdf_geo}
                                 apertures_dict['0']['geometry'].append(deepcopy(new_el))
                 else:
                     try:
                         for pdf_geo in path_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in fill_geo:
-                                    new_el = {}
-                                    new_el['solid'] = poly
-                                    new_el['follow'] = poly.exterior
+                                    new_el = {'solid': poly, 'follow': poly.exterior}
                                     apertures_dict['0']['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = {}
-                                new_el['solid'] = pdf_geo
-                                new_el['follow'] = pdf_geo.exterior
+                                new_el = {'solid': pdf_geo, 'follow': pdf_geo.exterior}
                                 apertures_dict['0']['geometry'].append(deepcopy(new_el))
                     except KeyError:
                         # in case there is no stroke width yet therefore no aperture
-                        apertures_dict['0'] = {}
-                        apertures_dict['0']['size'] = round(applied_size, 5)
-                        apertures_dict['0']['type'] = 'C'
-                        apertures_dict['0']['geometry'] = []
+                        apertures_dict['0'] = {
+                            'size': round(applied_size, 5),
+                            'type': 'C',
+                            'geometry': []
+                        }
+
                         for pdf_geo in fill_geo:
                             if isinstance(pdf_geo, MultiPolygon):
                                 for poly in pdf_geo:
-                                    new_el = {}
-                                    new_el['solid'] = poly
-                                    new_el['follow'] = poly.exterior
+                                    new_el = {'solid': poly, 'follow': poly.exterior}
                                     apertures_dict['0']['geometry'].append(deepcopy(new_el))
                             else:
-                                new_el = {}
-                                new_el['solid'] = pdf_geo
-                                new_el['follow'] = pdf_geo.exterior
+                                new_el = {'solid': pdf_geo, 'follow': pdf_geo.exterior}
                                 apertures_dict['0']['geometry'].append(deepcopy(new_el))
 
                 continue

+ 5 - 1
appParsers/ParseSVG.py

@@ -24,7 +24,7 @@ from svg.path import Line, Arc, CubicBezier, QuadraticBezier, parse_path
 # from svg.path.path import Move
 # from svg.path.path import Close
 import svg.path
-from shapely.geometry import LineString, MultiLineString
+from shapely.geometry import LineString, MultiLineString, Point
 from shapely.affinity import skew, affine_transform, rotate
 import numpy as np
 
@@ -286,6 +286,8 @@ def svgcircle2shapely(circle, n_points=64, factor=1.0):
     :type circle:       xml.etree.ElementTree.Element
     :param n_points:    circle resolution; nr of points to b e used to approximate a circle
     :type n_points:     int
+    :param factor:
+    :type factor:       float
     :return:            Shapely representation of the circle.
     :rtype:             shapely.geometry.polygon.LinearRing
     """
@@ -310,6 +312,8 @@ def svgellipse2shapely(ellipse, n_points=64, factor=1.0):
     :type ellipse:      xml.etree.ElementTree.Element
     :param n_points:    Number of discrete points in output.
     :type n_points:     int
+    :param factor:
+    :type factor:       float
     :return:            Shapely representation of the ellipse.
     :rtype:             shapely.geometry.polygon.LinearRing
     """

+ 4 - 2
appPreProcessor.py

@@ -147,8 +147,10 @@ class AppPreProcTools(object, metaclass=ABCPreProcRegister):
 
 
 def load_preprocessors(app):
-    preprocessors_path_search = [os.path.join(app.data_path, 'preprocessors', '*.py'),
-                                  os.path.join('preprocessors', '*.py')]
+    preprocessors_path_search = [
+        os.path.join(app.data_path, 'preprocessors', '*.py'),
+        os.path.join('preprocessors', '*.py')
+    ]
     import glob
     for path_search in preprocessors_path_search:
         for file in glob.glob(path_search):

+ 5 - 4
appTools/ToolAlignObjects.py

@@ -242,7 +242,7 @@ class AlignObjects(AppTool):
     def check_points(self):
         if len(self.clicked_points) == 1:
             self.app.inform.emit('%s: %s. %s' % (
-                _("First Point"), _("Click on the DESTINATION point."), _("Or right click to cancel.")))
+                _("First Point"), _("Click on the DESTINATION point ..."), _("Or right click to cancel.")))
             self.target_obj = self.aligner_obj
             self.reset_color()
             self.set_color()
@@ -264,7 +264,7 @@ class AlignObjects(AppTool):
 
         if len(self.clicked_points) == 3:
             self.app.inform.emit('%s: %s. %s' % (
-                _("Second Point"), _("Click on the DESTINATION point."), _("Or right click to cancel.")))
+                _("Second Point"), _("Click on the DESTINATION point ..."), _("Or right click to cancel.")))
             self.target_obj = self.aligner_obj
             self.reset_color()
             self.set_color()
@@ -383,7 +383,7 @@ class AlignUI:
         grid0.setColumnStretch(1, 1)
         self.layout.addLayout(grid0)
 
-        self.aligned_label =FCLabel('<b>%s:</b>' % _("MOVING object"))
+        self.aligned_label = FCLabel('<b>%s:</b>' % _("MOVING object"))
         grid0.addWidget(self.aligned_label, 0, 0, 1, 2)
 
         self.aligned_label.setToolTip(
@@ -478,7 +478,8 @@ class AlignUI:
         grid0.addWidget(separator_line, 14, 0, 1, 2)
 
         # Buttons
-        self.align_object_button =FCButton(_("Align Object"))
+        self.align_object_button = FCButton(_("Align Object"))
+        self.align_object_button.setIcon(QtGui.QIcon(self.app.resource_location + '/align16.png'))
         self.align_object_button.setToolTip(
             _("Align the specified object to the aligner object.\n"
               "If only one point is used then it assumes translation.\n"

+ 197 - 68
appTools/ToolCalculators.py

@@ -7,7 +7,7 @@
 
 from PyQt5 import QtWidgets, QtGui
 from appTool import AppTool
-from appGUI.GUIElements import FCSpinner, FCDoubleSpinner, NumericalEvalEntry
+from appGUI.GUIElements import FCSpinner, FCDoubleSpinner, NumericalEvalEntry, FCLabel, RadioSet, FCButton
 import math
 
 import gettext
@@ -48,6 +48,8 @@ class ToolCalculator(AppTool):
         self.ui.calculate_plate_button.clicked.connect(self.on_calculate_eplate)
         self.ui.reset_button.clicked.connect(self.set_tool_ui)
 
+        self.ui.area_sel_radio.activated_custom.connect(self.on_area_calculation_radio)
+
     def run(self, toggle=True):
         self.app.defaults.report_usage("ToolCalculators()")
 
@@ -80,7 +82,7 @@ class ToolCalculator(AppTool):
         AppTool.install(self, icon, separator, shortcut='Alt+C', **kwargs)
 
     def set_tool_ui(self):
-        self.units = self.app.defaults['units'].upper()
+        self.units = self.app.defaults['units'].lower()
 
         # ## Initialize form
         self.ui.mm_entry.set_value('%.*f' % (self.decimals, 0))
@@ -90,8 +92,10 @@ class ToolCalculator(AppTool):
         width = self.app.defaults["tools_calc_electro_width"]
         density = self.app.defaults["tools_calc_electro_cdensity"]
         growth = self.app.defaults["tools_calc_electro_growth"]
+
         self.ui.pcblength_entry.set_value(length)
         self.ui.pcbwidth_entry.set_value(width)
+        self.ui.area_entry.set_value(self.app.defaults["tools_calc_electro_area"])
         self.ui.cdensity_entry.set_value(density)
         self.ui.growth_entry.set_value(growth)
         self.ui.cvalue_entry.set_value(0.00)
@@ -106,6 +110,35 @@ class ToolCalculator(AppTool):
         self.ui.cutDepth_entry.set_value(cut_z)
         self.ui.effectiveToolDia_entry.set_value('0.0000')
 
+        self.ui.area_sel_radio.set_value('d')
+        self.on_area_calculation_radio(val='d')
+
+    def on_area_calculation_radio(self, val):
+        if val == 'a':
+            self.ui.pcbwidthlabel.hide()
+            self.ui.pcbwidth_entry.hide()
+            self.ui.width_unit.hide()
+
+            self.ui.pcblengthlabel.hide()
+            self.ui.pcblength_entry.hide()
+            self.ui.length_unit.hide()
+
+            self.ui.area_label.show()
+            self.ui.area_entry.show()
+            self.ui.area_unit.show()
+        else:
+            self.ui.pcbwidthlabel.show()
+            self.ui.pcbwidth_entry.show()
+            self.ui.width_unit.show()
+
+            self.ui.pcblengthlabel.show()
+            self.ui.pcblength_entry.show()
+            self.ui.length_unit.show()
+
+            self.ui.area_label.hide()
+            self.ui.area_entry.hide()
+            self.ui.area_unit.hide()
+
     def on_calculate_tool_dia(self):
         # Calculation:
         # Manufacturer gives total angle of the the tip but we need only half of it
@@ -123,23 +156,29 @@ class ToolCalculator(AppTool):
         cut_depth = -cut_depth if cut_depth < 0 else cut_depth
 
         tool_diameter = tip_diameter + (2 * cut_depth * math.tan(math.radians(half_tip_angle)))
-        self.ui.effectiveToolDia_entry.set_value("%.*f" % (self.decimals, tool_diameter))
+        self.ui.effectiveToolDia_entry.set_value(self.app.dec_format(tool_diameter, self.decimals))
 
     def on_calculate_inch_units(self):
-        mm_val = float(self.mm_entry.get_value())
+        mm_val = float(self.ui.mm_entry.get_value())
         self.ui.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())
+        inch_val = float(self.ui.inch_entry.get_value())
         self.ui.mm_entry.set_value('%.*f' % (self.decimals, (inch_val * 25.4)))
 
     def on_calculate_eplate(self):
-        length = float(self.ui.pcblength_entry.get_value())
-        width = float(self.ui.pcbwidth_entry.get_value())
-        density = float(self.ui.cdensity_entry.get_value())
-        copper = float(self.ui.growth_entry.get_value())
+        area_calc_sel = self.ui.area_sel_radio.get_value()
+        length = self.ui.pcblength_entry.get_value()
+        width = self.ui.pcbwidth_entry.get_value()
+        area = self.ui.area_entry.get_value()
+
+        density = self.ui.cdensity_entry.get_value()
+        copper = self.ui.growth_entry.get_value()
 
-        calculated_current = (length * width * density) * 0.0021527820833419
+        if area_calc_sel == 'd':
+            calculated_current = (length * width * density) * 0.0021527820833419
+        else:
+            calculated_current = (area * density) * 0.0021527820833419
         calculated_time = copper * 2.142857142857143 * float(20 / density)
 
         self.ui.cvalue_entry.set_value('%.2f' % calculated_current)
@@ -157,9 +196,10 @@ class CalcUI:
         self.app = app
         self.decimals = self.app.decimals
         self.layout = layout
+        self.units = self.app.defaults['units'].lower()
 
         # ## Title
-        title_label = QtWidgets.QLabel("%s" % self.toolName)
+        title_label = FCLabel("%s" % self.toolName)
         title_label.setStyleSheet("""
                                 QLabel
                                 {
@@ -173,19 +213,19 @@ class CalcUI:
         # ## Units Calculator #
         # #####################
 
-        self.unists_spacer_label = QtWidgets.QLabel(" ")
+        self.unists_spacer_label = FCLabel(" ")
         self.layout.addWidget(self.unists_spacer_label)
 
         # ## Title of the Units Calculator
-        units_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.unitsName)
+        units_label = FCLabel("<font size=3><b>%s</b></font>" % self.unitsName)
         self.layout.addWidget(units_label)
 
         # Grid Layout
         grid_units_layout = QtWidgets.QGridLayout()
         self.layout.addLayout(grid_units_layout)
 
-        inch_label = QtWidgets.QLabel(_("INCH"))
-        mm_label = QtWidgets.QLabel(_("MM"))
+        inch_label = FCLabel(_("INCH"))
+        mm_label = FCLabel(_("MM"))
         grid_units_layout.addWidget(mm_label, 0, 0)
         grid_units_layout.addWidget(inch_label, 0, 1)
 
@@ -206,21 +246,21 @@ class CalcUI:
         # ##############################
         # ## V-shape Tool Calculator ###
         # ##############################
-        self.v_shape_spacer_label = QtWidgets.QLabel(" ")
+        self.v_shape_spacer_label = FCLabel(" ")
         self.layout.addWidget(self.v_shape_spacer_label)
 
         # ## Title of the V-shape Tools Calculator
-        v_shape_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.v_shapeName)
+        v_shape_title_label = FCLabel("<font size=3><b>%s</b></font>" % self.v_shapeName)
         self.layout.addWidget(v_shape_title_label)
 
         # ## Form Layout
         form_layout = QtWidgets.QFormLayout()
         self.layout.addLayout(form_layout)
 
-        self.tipDia_label = QtWidgets.QLabel('%s:' % _("Tip Diameter"))
+        self.tipDia_label = FCLabel('%s:' % _("Tip Diameter"))
         self.tipDia_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.tipDia_entry.set_precision(self.decimals)
-        self.tipDia_entry.set_range(0.0, 9999.9999)
+        self.tipDia_entry.set_range(0.0, 10000.0000)
         self.tipDia_entry.setSingleStep(0.1)
 
         # self.tipDia_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
@@ -228,7 +268,7 @@ class CalcUI:
             _("This is the tool tip diameter.\n"
               "It is specified by manufacturer.")
         )
-        self.tipAngle_label = QtWidgets.QLabel('%s:' % _("Tip Angle"))
+        self.tipAngle_label = FCLabel('%s:' % _("Tip Angle"))
         self.tipAngle_entry = FCSpinner(callback=self.confirmation_message_int)
         self.tipAngle_entry.set_range(0, 180)
         self.tipAngle_entry.set_step(5)
@@ -237,16 +277,16 @@ class CalcUI:
         self.tipAngle_label.setToolTip(_("This is the angle of the tip of the tool.\n"
                                          "It is specified by manufacturer."))
 
-        self.cutDepth_label = QtWidgets.QLabel('%s:' % _("Cut Z"))
+        self.cutDepth_label = FCLabel('%s:' % _("Cut Z"))
         self.cutDepth_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.cutDepth_entry.set_range(-9999.9999, 9999.9999)
+        self.cutDepth_entry.set_range(-10000.0000, 10000.0000)
         self.cutDepth_entry.set_precision(self.decimals)
 
         # self.cutDepth_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.cutDepth_label.setToolTip(_("This is the depth to cut into the material.\n"
                                          "In the CNCJob is the CutZ parameter."))
 
-        self.effectiveToolDia_label = QtWidgets.QLabel('%s:' % _("Tool Diameter"))
+        self.effectiveToolDia_label = FCLabel('%s:' % _("Tool Diameter"))
         self.effectiveToolDia_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.effectiveToolDia_entry.set_precision(self.decimals)
 
@@ -262,8 +302,9 @@ class CalcUI:
         form_layout.addRow(self.effectiveToolDia_label, self.effectiveToolDia_entry)
 
         # ## Buttons
-        self.calculate_vshape_button = QtWidgets.QPushButton(_("Calculate"))
-        # self.calculate_button.setFixedWidth(70)
+        self.calculate_vshape_button = FCButton(_("Calculate"))
+        self.calculate_vshape_button.setIcon(QtGui.QIcon(self.app.resource_location + '/calculator16.png'))
+
         self.calculate_vshape_button.setToolTip(
             _("Calculate either the Cut Z or the effective tool diameter,\n  "
               "depending on which is desired and which is known. ")
@@ -275,11 +316,10 @@ class CalcUI:
         # ## ElectroPlating Tool Calculator ##
         # ####################################
 
-        self.plate_spacer_label = QtWidgets.QLabel(" ")
-        self.layout.addWidget(self.plate_spacer_label)
+        self.layout.addWidget(FCLabel(""))
 
         # ## Title of the ElectroPlating Tools Calculator
-        plate_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.eplateName)
+        plate_title_label = FCLabel("<font size=3><b>%s</b></font>" % self.eplateName)
         plate_title_label.setToolTip(
             _("This calculator is useful for those who plate the via/pad/drill holes,\n"
               "using a method like graphite ink or calcium hypophosphite ink or palladium chloride.")
@@ -287,79 +327,168 @@ class CalcUI:
         self.layout.addWidget(plate_title_label)
 
         # ## Plate Form Layout
-        plate_form_layout = QtWidgets.QFormLayout()
-        self.layout.addLayout(plate_form_layout)
+        grid2 = QtWidgets.QGridLayout()
+        grid2.setColumnStretch(0, 0)
+        grid2.setColumnStretch(1, 1)
+        self.layout.addLayout(grid2)
+
+        # Area Calculation
+        self.area_sel_label = FCLabel('%s:' % _("Area Calculation"))
+        self.area_sel_label.setToolTip(
+            _("Choose how to calculate the board area.")
+        )
+        self.area_sel_radio = RadioSet([
+            {'label': _('Dimensions'), 'value': 'd'},
+            {"label": _("Area"), "value": "a"}
+        ], stretch=False)
 
-        self.pcblengthlabel = QtWidgets.QLabel('%s:' % _("Board Length"))
+        grid2.addWidget(self.area_sel_label, 0, 0)
+        grid2.addWidget(self.area_sel_radio, 1, 0, 1, 2)
+
+        # BOARD LENGTH
+        self.pcblengthlabel = FCLabel('%s:' % _("Board Length"))
+        self.pcblengthlabel.setToolTip(_('This is the board length. In centimeters.'))
         self.pcblength_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.pcblength_entry.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
         self.pcblength_entry.set_precision(self.decimals)
-        self.pcblength_entry.set_range(0.0, 9999.9999)
+        self.pcblength_entry.set_range(0.0, 10000.0000)
 
-        # self.pcblength_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
-        self.pcblengthlabel.setToolTip(_('This is the board length. In centimeters.'))
+        self.length_unit = FCLabel('%s' % _("cm"))
+        self.length_unit.setMinimumWidth(25)
 
-        self.pcbwidthlabel = QtWidgets.QLabel('%s:' % _("Board Width"))
+        l_hlay = QtWidgets.QHBoxLayout()
+        l_hlay.addWidget(self.pcblength_entry)
+        l_hlay.addWidget(self.length_unit)
+
+        grid2.addWidget(self.pcblengthlabel, 2, 0)
+        grid2.addLayout(l_hlay, 2, 1)
+
+        # BOARD WIDTH
+        self.pcbwidthlabel = FCLabel('%s:' % _("Board Width"))
+        self.pcbwidthlabel.setToolTip(_('This is the board width.In centimeters.'))
         self.pcbwidth_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.pcbwidth_entry.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
         self.pcbwidth_entry.set_precision(self.decimals)
-        self.pcbwidth_entry.set_range(0.0, 9999.9999)
+        self.pcbwidth_entry.set_range(0.0, 10000.0000)
 
-        # self.pcbwidth_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
-        self.pcbwidthlabel.setToolTip(_('This is the board width.In centimeters.'))
+        self.width_unit = FCLabel('%s' % _("cm"))
+        self.width_unit.setMinimumWidth(25)
+
+        w_hlay = QtWidgets.QHBoxLayout()
+        w_hlay.addWidget(self.pcbwidth_entry)
+        w_hlay.addWidget(self.width_unit)
+
+        grid2.addWidget(self.pcbwidthlabel, 4, 0)
+        grid2.addLayout(w_hlay, 4, 1)
+
+        # AREA
+        self.area_label = FCLabel('%s:' % _("Area"))
+        self.area_label.setToolTip(_('This is the board area.'))
+        self.area_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.area_entry.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
+        self.area_entry.set_precision(self.decimals)
+        self.area_entry.set_range(0.0, 10000.0000)
+
+        self.area_unit = FCLabel('%s<sup>2</sup>' % _("cm"))
+        self.area_unit.setMinimumWidth(25)
+
+        a_hlay = QtWidgets.QHBoxLayout()
+        a_hlay.addWidget(self.area_entry)
+        a_hlay.addWidget(self.area_unit)
 
-        self.cdensity_label = QtWidgets.QLabel('%s:' % _("Current Density"))
+        grid2.addWidget(self.area_label, 6, 0)
+        grid2.addLayout(a_hlay, 6, 1)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid2.addWidget(separator_line, 7, 0, 1, 2)
+
+        # DENSITY
+        self.cdensity_label = FCLabel('%s:' % _("Current Density"))
+        self.cdensity_label.setToolTip(_("Current density to pass through the board. \n"
+                                         "In Amps per Square Feet ASF."))
         self.cdensity_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.cdensity_entry.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
         self.cdensity_entry.set_precision(self.decimals)
-        self.cdensity_entry.set_range(0.0, 9999.9999)
+        self.cdensity_entry.set_range(0.0, 10000.0000)
         self.cdensity_entry.setSingleStep(0.1)
 
-        # self.cdensity_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
-        self.cdensity_label.setToolTip(_("Current density to pass through the board. \n"
-                                         "In Amps per Square Feet ASF."))
+        density_unit = FCLabel('%s' % "ASF")
+        density_unit.setMinimumWidth(25)
 
-        self.growth_label = QtWidgets.QLabel('%s:' % _("Copper Growth"))
+        d_hlay = QtWidgets.QHBoxLayout()
+        d_hlay.addWidget(self.cdensity_entry)
+        d_hlay.addWidget(density_unit)
+
+        grid2.addWidget(self.cdensity_label, 8, 0)
+        grid2.addLayout(d_hlay, 8, 1)
+
+        # COPPER GROWTH
+        self.growth_label = FCLabel('%s:' % _("Copper Growth"))
+        self.growth_label.setToolTip(_("How thick the copper growth is intended to be.\n"
+                                       "In microns."))
         self.growth_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.growth_entry.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
         self.growth_entry.set_precision(self.decimals)
-        self.growth_entry.set_range(0.0, 9999.9999)
+        self.growth_entry.set_range(0.0, 10000.0000)
         self.growth_entry.setSingleStep(0.01)
 
-        # self.growth_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
-        self.growth_label.setToolTip(_("How thick the copper growth is intended to be.\n"
-                                       "In microns."))
+        growth_unit = FCLabel('%s' % _("um"))
+        growth_unit.setMinimumWidth(25)
 
-        # self.growth_entry.setEnabled(False)
+        g_hlay = QtWidgets.QHBoxLayout()
+        g_hlay.addWidget(self.growth_entry)
+        g_hlay.addWidget(growth_unit)
 
-        self.cvaluelabel = QtWidgets.QLabel('%s:' % _("Current Value"))
+        grid2.addWidget(self.growth_label, 10, 0)
+        grid2.addLayout(g_hlay, 10, 1)
+
+        # CURRENT
+        self.cvaluelabel = FCLabel('%s:' % _("Current Value"))
+        self.cvaluelabel.setToolTip(_('This is the current intensity value\n'
+                                      'to be set on the Power Supply. In Amps.'))
         self.cvalue_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.cvalue_entry.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
         self.cvalue_entry.set_precision(self.decimals)
-        self.cvalue_entry.set_range(0.0, 9999.9999)
+        self.cvalue_entry.set_range(0.0, 10000.0000)
         self.cvalue_entry.setSingleStep(0.1)
 
-        # self.cvalue_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
-        self.cvaluelabel.setToolTip(_('This is the current intensity value\n'
-                                      'to be set on the Power Supply. In Amps.'))
+        current_unit = FCLabel('%s' % "A")
+        current_unit.setMinimumWidth(25)
         self.cvalue_entry.setReadOnly(True)
 
-        self.timelabel = QtWidgets.QLabel('%s:' % _("Time"))
+        c_hlay = QtWidgets.QHBoxLayout()
+        c_hlay.addWidget(self.cvalue_entry)
+        c_hlay.addWidget(current_unit)
+
+        grid2.addWidget(self.cvaluelabel, 12, 0)
+        grid2.addLayout(c_hlay, 12, 1)
+
+        # TIME
+        self.timelabel = FCLabel('%s:' % _("Time"))
+        self.timelabel.setToolTip(_('This is the calculated time required for the procedure.\n'
+                                    'In minutes.'))
         self.time_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.time_entry.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
         self.time_entry.set_precision(self.decimals)
-        self.time_entry.set_range(0.0, 9999.9999)
+        self.time_entry.set_range(0.0, 10000.0000)
         self.time_entry.setSingleStep(0.1)
 
-        # self.time_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
-        self.timelabel.setToolTip(_('This is the calculated time required for the procedure.\n'
-                                    'In minutes.'))
+        time_unit = FCLabel('%s' % "min")
+        time_unit.setMinimumWidth(25)
         self.time_entry.setReadOnly(True)
 
-        plate_form_layout.addRow(self.pcblengthlabel, self.pcblength_entry)
-        plate_form_layout.addRow(self.pcbwidthlabel, self.pcbwidth_entry)
-        plate_form_layout.addRow(self.cdensity_label, self.cdensity_entry)
-        plate_form_layout.addRow(self.growth_label, self.growth_entry)
-        plate_form_layout.addRow(self.cvaluelabel, self.cvalue_entry)
-        plate_form_layout.addRow(self.timelabel, self.time_entry)
+        t_hlay = QtWidgets.QHBoxLayout()
+        t_hlay.addWidget(self.time_entry)
+        t_hlay.addWidget(time_unit)
+
+        grid2.addWidget(self.timelabel, 14, 0)
+        grid2.addLayout(t_hlay, 14, 1)
 
         # ## Buttons
-        self.calculate_plate_button = QtWidgets.QPushButton(_("Calculate"))
-        # self.calculate_button.setFixedWidth(70)
+        self.calculate_plate_button = FCButton(_("Calculate"))
+        self.calculate_plate_button.setIcon(QtGui.QIcon(self.app.resource_location + '/calculator16.png'))
         self.calculate_plate_button.setToolTip(
             _("Calculate the current intensity value and the procedure time,\n"
               "depending on the parameters above")
@@ -369,7 +498,7 @@ class CalcUI:
         self.layout.addStretch()
 
         # ## Reset Tool
-        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
+        self.reset_button = FCButton(_("Reset Tool"))
         self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png'))
         self.reset_button.setToolTip(
             _("Will reset the tool parameters.")

+ 21 - 21
appTools/ToolCalibration.py

@@ -291,7 +291,7 @@ class ToolCalibration(AppTool):
         elif len(self.click_points) == 4:
             self.ui.top_right_coordx_tgt.set_value(self.click_points[3][0])
             self.ui.top_right_coordy_tgt.set_value(self.click_points[3][1])
-            self.app.inform.emit('[success] %s' % _("Done. All four points have been acquired."))
+            self.app.inform.emit('[success] %s' % _("Done."))
             self.disconnect_cal_events()
 
     def reset_calibration_points(self):
@@ -600,7 +600,7 @@ class ToolCalibration(AppTool):
             self.cal_object = model_index.internalPointer().obj
         except Exception as e:
             log.debug("ToolCalibration.on_cal_button_click() --> %s" % str(e))
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no FlatCAM object selected..."))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("No object is selected."))
             return 'fail'
 
         obj_name = self.cal_object.options["name"] + "_calibrated"
@@ -639,7 +639,7 @@ class ToolCalibration(AppTool):
                 if obj.tools:
                     obj_init.tools = deepcopy(obj.tools)
             except Exception as ee:
-                log.debug("ToolCalibration.new_calibrated_object.initialize_geometry() --> %s" % str(ee))
+                app.log.debug("ToolCalibration.new_calibrated_object.initialize_geometry() --> %s" % str(ee))
 
             obj_init.scale(xfactor=scalex, yfactor=scaley, point=(origin_x, origin_y))
             obj_init.skew(angle_x=skewx, angle_y=skewy, point=(origin_x, origin_y))
@@ -649,7 +649,7 @@ class ToolCalibration(AppTool):
             except (AttributeError, TypeError):
                 pass
 
-        def initialize_gerber(obj_init, app):
+        def initialize_gerber(obj_init, app_obj):
             obj_init.solid_geometry = deepcopy(obj.solid_geometry)
             try:
                 obj_init.follow_geometry = deepcopy(obj.follow_geometry)
@@ -671,12 +671,12 @@ class ToolCalibration(AppTool):
             obj_init.skew(angle_x=skewx, angle_y=skewy, point=(origin_x, origin_y))
 
             try:
-                obj_init.source_file = self.app.f_handlers.export_gerber(obj_name=obj_name, filename=None,
-                                                                         local_use=obj_init, use_thread=False)
+                obj_init.source_file = app_obj.f_handlers.export_gerber(obj_name=obj_name, filename=None,
+                                                                        local_use=obj_init, use_thread=False)
             except (AttributeError, TypeError):
                 pass
 
-        def initialize_excellon(obj_init, app):
+        def initialize_excellon(obj_init, app_obj):
             obj_init.tools = deepcopy(obj.tools)
 
             # drills are offset, so they need to be deep copied
@@ -689,14 +689,14 @@ class ToolCalibration(AppTool):
 
             obj_init.create_geometry()
 
-            obj_init.source_file = self.app.export.export_excellon(obj_name=obj_name, local_use=obj, filename=None,
-                                                                   use_thread=False)
+            obj_init.source_file = app_obj.f_handlers.export_excellon(obj_name=obj_name, local_use=obj, filename=None,
+                                                                      use_thread=False)
 
         obj = self.cal_object
         obj_name = obj_name
 
         if obj is None:
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no FlatCAM object selected..."))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("No object is selected."))
             log.debug("ToolCalibration.new_calibrated_object() --> No object to calibrate")
             return 'fail'
 
@@ -772,7 +772,7 @@ class CalibUI:
         )
 
         self.travelz_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.travelz_entry.set_range(-9999.9999, 9999.9999)
+        self.travelz_entry.set_range(-10000.0000, 10000.0000)
         self.travelz_entry.set_precision(self.decimals)
         self.travelz_entry.setSingleStep(0.1)
 
@@ -786,7 +786,7 @@ class CalibUI:
         )
 
         self.verz_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.verz_entry.set_range(-9999.9999, 9999.9999)
+        self.verz_entry.set_range(-10000.0000, 10000.0000)
         self.verz_entry.set_precision(self.decimals)
         self.verz_entry.setSingleStep(0.1)
 
@@ -809,7 +809,7 @@ class CalibUI:
         )
 
         self.toolchangez_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.toolchangez_entry.set_range(0.0000, 9999.9999)
+        self.toolchangez_entry.set_range(0.0000, 10000.0000)
         self.toolchangez_entry.set_precision(self.decimals)
         self.toolchangez_entry.setSingleStep(0.1)
 
@@ -851,8 +851,8 @@ class CalibUI:
               "- top-left -> the user will align the PCB vertically\n"
               "- bottom-right -> the user will align the PCB horizontally")
         )
-        self.second_point_radio = RadioSet([{'label': _('Top-Left'), 'value': 'tl'},
-                                            {'label': _('Bottom-Right'), 'value': 'br'}],
+        self.second_point_radio = RadioSet([{'label': _('Top Left'), 'value': 'tl'},
+                                            {'label': _('Bottom Right'), 'value': 'br'}],
                                            orientation='vertical')
 
         grid_lay.addWidget(second_point_lbl, 7, 0)
@@ -1164,7 +1164,7 @@ class CalibUI:
             _("Factor for Scale action over X axis.")
         )
         self.scalex_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.scalex_entry.set_range(0, 9999.9999)
+        self.scalex_entry.set_range(0, 10000.0000)
         self.scalex_entry.set_precision(self.decimals)
         self.scalex_entry.setSingleStep(0.1)
 
@@ -1176,7 +1176,7 @@ class CalibUI:
             _("Factor for Scale action over Y axis.")
         )
         self.scaley_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.scaley_entry.set_range(0, 9999.9999)
+        self.scaley_entry.set_range(0, 10000.0000)
         self.scaley_entry.set_precision(self.decimals)
         self.scaley_entry.setSingleStep(0.1)
 
@@ -1197,7 +1197,7 @@ class CalibUI:
 
         self.skewx_label = QtWidgets.QLabel(_("Skew Angle X:"))
         self.skewx_label.setToolTip(
-            _("Angle for Skew action, in degrees.\n"
+            _("Angle, in degrees.\n"
               "Float number between -360 and 359.")
         )
         self.skewx_entry = FCDoubleSpinner(callback=self.confirmation_message)
@@ -1210,7 +1210,7 @@ class CalibUI:
 
         self.skewy_label = QtWidgets.QLabel(_("Skew Angle Y:"))
         self.skewy_label.setToolTip(
-            _("Angle for Skew action, in degrees.\n"
+            _("Angle, in degrees.\n"
               "Float number between -360 and 359.")
         )
         self.skewy_entry = FCDoubleSpinner(callback=self.confirmation_message)
@@ -1245,7 +1245,7 @@ class CalibUI:
         #     _("Final factor for Scale action over X axis.")
         # )
         # self.fin_scalex_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        # self.fin_scalex_entry.set_range(0, 9999.9999)
+        # self.fin_scalex_entry.set_range(0, 10000.0000)
         # self.fin_scalex_entry.set_precision(self.decimals)
         # self.fin_scalex_entry.setSingleStep(0.1)
         #
@@ -1257,7 +1257,7 @@ class CalibUI:
         #     _("Final factor for Scale action over Y axis.")
         # )
         # self.fin_scaley_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        # self.fin_scaley_entry.set_range(0, 9999.9999)
+        # self.fin_scaley_entry.set_range(0, 10000.0000)
         # self.fin_scaley_entry.set_precision(self.decimals)
         # self.fin_scaley_entry.setSingleStep(0.1)
         #

File diff suppressed because it is too large
+ 339 - 280
appTools/ToolCopperThieving.py


+ 310 - 98
appTools/ToolCorners.py

@@ -8,9 +8,10 @@
 from PyQt5 import QtWidgets, QtCore, QtGui
 
 from appTool import AppTool
-from appGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCComboBox, FCButton
+from appGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCComboBox, FCButton, RadioSet, FCLabel
 
-from shapely.geometry import MultiPolygon, LineString
+from shapely.geometry import MultiPolygon, LineString, Point
+from shapely.ops import unary_union
 
 from copy import deepcopy
 import logging
@@ -37,6 +38,9 @@ class ToolCorners(AppTool):
         self.decimals = self.app.decimals
         self.units = ''
 
+        # here we store the locations of the selected corners
+        self.points = {}
+
         # #############################################################################
         # ######################### Tool GUI ##########################################
         # #############################################################################
@@ -57,6 +61,7 @@ class ToolCorners(AppTool):
         # SIGNALS
         self.ui.add_marker_button.clicked.connect(self.add_markers)
         self.ui.toggle_all_cb.toggled.connect(self.on_toggle_all)
+        self.ui.drill_button.clicked.connect(self.on_create_drill_object)
 
     def run(self, toggle=True):
         self.app.defaults.report_usage("ToolCorners()")
@@ -95,6 +100,8 @@ class ToolCorners(AppTool):
         self.ui.l_entry.set_value(float(self.app.defaults["tools_corners_length"]))
         self.ui.margin_entry.set_value(float(self.app.defaults["tools_corners_margin"]))
         self.ui.toggle_all_cb.set_value(False)
+        self.ui.type_radio.set_value(self.app.defaults["tools_corners_type"])
+        self.ui.drill_dia_entry.set_value(self.app.defaults["tools_corners_drill_dia"])
 
     def on_toggle_all(self, val):
         self.ui.bl_cb.set_value(val)
@@ -118,26 +125,28 @@ class ToolCorners(AppTool):
         except Exception as e:
             log.debug("ToolCorners.add_markers() --> %s" % str(e))
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
+            self.app.call_source = "app"
             return
 
         xmin, ymin, xmax, ymax = self.grb_object.bounds()
-        points = {}
+        self.points = {}
         if tl_state:
-            points['tl'] = (xmin, ymax)
+            self.points['tl'] = (xmin, ymax)
         if tr_state:
-            points['tr'] = (xmax, ymax)
+            self.points['tr'] = (xmax, ymax)
         if bl_state:
-            points['bl'] = (xmin, ymin)
+            self.points['bl'] = (xmin, ymin)
         if br_state:
-            points['br'] = (xmax, ymin)
+            self.points['br'] = (xmax, ymin)
 
-        self.add_corners_geo(points, g_obj=self.grb_object)
+        ret_val = self.add_corners_geo(self.points, g_obj=self.grb_object)
+        self.app.call_source = "app"
+        if ret_val == 'fail':
+            self.app.call_source = "app"
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed."))
+            return
 
-        self.grb_object.source_file = self.app.f_handlers.export_gerber(obj_name=self.grb_object.options['name'],
-                                                                        filename=None,
-                                                                        local_use=self.grb_object,
-                                                                        use_thread=False)
-        self.on_exit()
+        self.on_exit(ret_val)
 
     def add_corners_geo(self, points_storage, g_obj):
         """
@@ -148,68 +157,103 @@ class ToolCorners(AppTool):
         :return:                None
         """
 
+        marker_type = self.ui.type_radio.get_value()
         line_thickness = self.ui.thick_entry.get_value()
-        line_length = self.ui.l_entry.get_value()
         margin = self.ui.margin_entry.get_value()
+        line_length = self.ui.l_entry.get_value() / 2.0
 
         geo_list = []
 
         if not points_storage:
             self.app.inform.emit("[ERROR_NOTCL] %s." % _("Please select at least a location"))
-            return
+            return 'fail'
 
         for key in points_storage:
             if key == 'tl':
                 pt = points_storage[key]
                 x = pt[0] - margin - line_thickness / 2.0
                 y = pt[1] + margin + line_thickness / 2.0
-                line_geo_hor = LineString([
-                    (x, y), (x + line_length, y)
-                ])
-                line_geo_vert = LineString([
-                    (x, y), (x, y - line_length)
-                ])
+                if marker_type == 's':
+                    line_geo_hor = LineString([
+                        (x, y), (x + line_length, y)
+                    ])
+                    line_geo_vert = LineString([
+                        (x, y), (x, y - line_length)
+                    ])
+                else:
+                    line_geo_hor = LineString([
+                        (x - line_length, y), (x + line_length, y)
+                    ])
+                    line_geo_vert = LineString([
+                        (x, y + line_length), (x, y - line_length)
+                    ])
                 geo_list.append(line_geo_hor)
                 geo_list.append(line_geo_vert)
             if key == 'tr':
                 pt = points_storage[key]
                 x = pt[0] + margin + line_thickness / 2.0
                 y = pt[1] + margin + line_thickness / 2.0
-                line_geo_hor = LineString([
-                    (x, y), (x - line_length, y)
-                ])
-                line_geo_vert = LineString([
-                    (x, y), (x, y - line_length)
-                ])
+                if marker_type == 's':
+                    line_geo_hor = LineString([
+                        (x, y), (x - line_length, y)
+                    ])
+                    line_geo_vert = LineString([
+                        (x, y), (x, y - line_length)
+                    ])
+                else:
+                    line_geo_hor = LineString([
+                        (x + line_length, y), (x - line_length, y)
+                    ])
+                    line_geo_vert = LineString([
+                        (x, y + line_length), (x, y - line_length)
+                    ])
                 geo_list.append(line_geo_hor)
                 geo_list.append(line_geo_vert)
             if key == 'bl':
                 pt = points_storage[key]
                 x = pt[0] - margin - line_thickness / 2.0
                 y = pt[1] - margin - line_thickness / 2.0
-                line_geo_hor = LineString([
-                    (x, y), (x + line_length, y)
-                ])
-                line_geo_vert = LineString([
-                    (x, y), (x, y + line_length)
-                ])
+                if marker_type == 's':
+                    line_geo_hor = LineString([
+                        (x, y), (x + line_length, y)
+                    ])
+                    line_geo_vert = LineString([
+                        (x, y), (x, y + line_length)
+                    ])
+                else:
+                    line_geo_hor = LineString([
+                        (x - line_length, y), (x + line_length, y)
+                    ])
+                    line_geo_vert = LineString([
+                        (x, y - line_length), (x, y + line_length)
+                    ])
                 geo_list.append(line_geo_hor)
                 geo_list.append(line_geo_vert)
             if key == 'br':
                 pt = points_storage[key]
                 x = pt[0] + margin + line_thickness / 2.0
                 y = pt[1] - margin - line_thickness / 2.0
-                line_geo_hor = LineString([
-                    (x, y), (x - line_length, y)
-                ])
-                line_geo_vert = LineString([
-                    (x, y), (x, y + line_length)
-                ])
+                if marker_type == 's':
+                    line_geo_hor = LineString([
+                        (x, y), (x - line_length, y)
+                    ])
+                    line_geo_vert = LineString([
+                        (x, y), (x, y + line_length)
+                    ])
+                else:
+                    line_geo_hor = LineString([
+                        (x + line_length, y), (x - line_length, y)
+                    ])
+                    line_geo_vert = LineString([
+                        (x, y - line_length), (x, y + line_length)
+                    ])
                 geo_list.append(line_geo_hor)
                 geo_list.append(line_geo_vert)
 
+        new_apertures = deepcopy(g_obj.apertures)
+
         aperture_found = None
-        for ap_id, ap_val in g_obj.apertures.items():
+        for ap_id, ap_val in new_apertures.items():
             if ap_val['type'] == 'C' and ap_val['size'] == line_thickness:
                 aperture_found = ap_id
                 break
@@ -220,30 +264,26 @@ class ToolCorners(AppTool):
                 geo_buff = geo.buffer(line_thickness / 2.0, resolution=self.grb_steps_per_circle, join_style=2)
                 geo_buff_list.append(geo_buff)
 
-                dict_el = {}
-                dict_el['follow'] = geo
-                dict_el['solid'] = geo_buff
-                g_obj.apertures[aperture_found]['geometry'].append(deepcopy(dict_el))
+                dict_el = {'follow': geo, 'solid': geo_buff}
+                new_apertures[aperture_found]['geometry'].append(deepcopy(dict_el))
         else:
-            ap_keys = list(g_obj.apertures.keys())
+            ap_keys = list(new_apertures.keys())
             if ap_keys:
                 new_apid = str(int(max(ap_keys)) + 1)
             else:
                 new_apid = '10'
 
-            g_obj.apertures[new_apid] = {}
-            g_obj.apertures[new_apid]['type'] = 'C'
-            g_obj.apertures[new_apid]['size'] = line_thickness
-            g_obj.apertures[new_apid]['geometry'] = []
+            new_apertures[new_apid] = {}
+            new_apertures[new_apid]['type'] = 'C'
+            new_apertures[new_apid]['size'] = line_thickness
+            new_apertures[new_apid]['geometry'] = []
 
             for geo in geo_list:
                 geo_buff = geo.buffer(line_thickness / 2.0, resolution=self.grb_steps_per_circle, join_style=3)
                 geo_buff_list.append(geo_buff)
 
-                dict_el = {}
-                dict_el['follow'] = geo
-                dict_el['solid'] = geo_buff
-                g_obj.apertures[new_apid]['geometry'].append(deepcopy(dict_el))
+                dict_el = {'follow': geo, 'solid': geo_buff}
+                new_apertures[new_apid]['geometry'].append(deepcopy(dict_el))
 
         s_list = []
         if g_obj.solid_geometry:
@@ -255,26 +295,146 @@ class ToolCorners(AppTool):
 
         geo_buff_list = MultiPolygon(geo_buff_list)
         geo_buff_list = geo_buff_list.buffer(0)
-        for poly in geo_buff_list:
-            s_list.append(poly)
-        g_obj.solid_geometry = MultiPolygon(s_list)
+        try:
+            for poly in geo_buff_list:
+                s_list.append(poly)
+        except TypeError:
+            s_list.append(geo_buff_list)
+
+        outname = '%s_%s' % (str(self.grb_object.options['name']), 'corners')
+
+        def initialize(grb_obj, app_obj):
+            grb_obj.options = {}
+            for opt in g_obj.options:
+                if opt != 'name':
+                    grb_obj.options[opt] = deepcopy(g_obj.options[opt])
+            grb_obj.options['name'] = outname
+            grb_obj.multitool = False
+            grb_obj.multigeo = False
+            grb_obj.follow = deepcopy(g_obj.follow)
+            grb_obj.apertures = new_apertures
+            grb_obj.solid_geometry = unary_union(s_list)
+            grb_obj.follow_geometry = deepcopy(g_obj.follow_geometry) + geo_list
+
+            grb_obj.source_file = app_obj.f_handlers.export_gerber(obj_name=outname, filename=None, local_use=grb_obj,
+                                                                   use_thread=False)
+
+        ret = self.app.app_obj.new_object('gerber', outname, initialize, plot=True)
+
+        return ret
+
+    def on_create_drill_object(self):
+        self.app.call_source = "corners_tool"
+
+        tooldia = self.ui.drill_dia_entry.get_value()
+
+        if tooldia == 0:
+            self.app.inform.emit('[WARNING_NOTCL] %s %s' % (_("Cancelled."), _("The tool diameter is zero.")))
+            return
+
+        line_thickness = self.ui.thick_entry.get_value()
+        margin = self.ui.margin_entry.get_value()
+        tl_state = self.ui.tl_cb.get_value()
+        tr_state = self.ui.tr_cb.get_value()
+        bl_state = self.ui.bl_cb.get_value()
+        br_state = self.ui.br_cb.get_value()
+
+        # get the Gerber object on which the corner marker will be inserted
+        selection_index = self.ui.object_combo.currentIndex()
+        model_index = self.app.collection.index(selection_index, 0, self.ui.object_combo.rootModelIndex())
+
+        try:
+            self.grb_object = model_index.internalPointer().obj
+        except Exception as e:
+            log.debug("ToolCorners.add_markers() --> %s" % str(e))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
+            self.app.call_source = "app"
+            return
+
+        if tl_state is False and tr_state is False and bl_state is False and br_state is False:
+            self.app.inform.emit("[ERROR_NOTCL] %s." % _("Please select at least a location"))
+            self.app.call_source = "app"
+            return
+
+        xmin, ymin, xmax, ymax = self.grb_object.bounds()
+
+        # list of (x,y) tuples. Store here the drill coordinates
+        drill_list = []
+
+        if tl_state:
+            x = xmin - margin - line_thickness / 2.0
+            y = ymax + margin + line_thickness / 2.0
+            drill_list.append(
+                Point((x, y))
+            )
+
+        if tr_state:
+            x = xmax + margin + line_thickness / 2.0
+            y = ymax + margin + line_thickness / 2.0
+            drill_list.append(
+                Point((x, y))
+            )
+
+        if bl_state:
+            x = xmin - margin - line_thickness / 2.0
+            y = ymin - margin - line_thickness / 2.0
+            drill_list.append(
+                Point((x, y))
+            )
+
+        if br_state:
+            x = xmax + margin + line_thickness / 2.0
+            y = ymin - margin - line_thickness / 2.0
+            drill_list.append(
+                Point((x, y))
+            )
+
+        tools = {1: {}}
+        tools[1]["tooldia"] = tooldia
+        tools[1]['drills'] = drill_list
+        tools[1]['solid_geometry'] = []
+
+        def obj_init(obj_inst, app_inst):
+            obj_inst.options.update({
+                'name': outname
+            })
+            obj_inst.tools = deepcopy(tools)
+            obj_inst.create_geometry()
+            obj_inst.source_file = app_inst.f_handlers.export_excellon(obj_name=obj_inst.options['name'],
+                                                                       local_use=obj_inst,
+                                                                       filename=None,
+                                                                       use_thread=False)
+
+        outname = '%s_%s' % (str(self.grb_object.options['name']), 'corner_drills')
+        ret_val = self.app.app_obj.new_object("excellon", outname, obj_init)
+
+        self.app.call_source = "app"
+        if ret_val == 'fail':
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed."))
+        else:
+            self.app.inform.emit('[success] %s' % _("Excellon object with corner drills created."))
 
     def replot(self, obj, run_thread=True):
         def worker_task():
-            with self.app.proc_container.new('%s...' % _("Plotting")):
+            with self.app.proc_container.new('%s ...' % _("Plotting")):
                 obj.plot()
+                self.app.app_obj.object_plotted.emit(obj)
 
         if run_thread:
             self.app.worker_task.emit({'fcn': worker_task, 'params': []})
         else:
             worker_task()
 
-    def on_exit(self):
+    def on_exit(self, corner_gerber_obj=None):
         # plot the object
-        try:
-            self.replot(obj=self.grb_object)
-        except (AttributeError, TypeError):
-            return
+        if corner_gerber_obj:
+            try:
+                for ob in corner_gerber_obj:
+                    self.replot(obj=ob)
+            except (AttributeError, TypeError):
+                self.replot(obj=corner_gerber_obj)
+            except Exception:
+                return
 
         # update the bounding box values
         try:
@@ -286,11 +446,8 @@ class ToolCorners(AppTool):
         except Exception as e:
             log.debug("ToolCorners.on_exit() copper_obj bounds error --> %s" % str(e))
 
-        # reset the variables
-        self.grb_object = None
-
         self.app.call_source = "app"
-        self.app.inform.emit('[success] %s' % _("Corners Tool exit."))
+        self.app.inform.emit('[success] %s' % _("A Gerber object with corner markers was created."))
 
 
 class CornersUI:
@@ -303,7 +460,7 @@ class CornersUI:
         self.layout = layout
 
         # ## Title
-        title_label = QtWidgets.QLabel("%s" % self.toolName)
+        title_label = FCLabel("%s" % self.toolName)
         title_label.setStyleSheet("""
                                 QLabel
                                 {
@@ -312,10 +469,10 @@ class CornersUI:
                                 }
                                 """)
         self.layout.addWidget(title_label)
-        self.layout.addWidget(QtWidgets.QLabel(""))
+        self.layout.addWidget(FCLabel(""))
 
         # Gerber object #
-        self.object_label = QtWidgets.QLabel('<b>%s:</b>' % _("GERBER"))
+        self.object_label = FCLabel('<b>%s:</b>' % _("GERBER"))
         self.object_label.setToolTip(
             _("The Gerber object to which will be added corner markers.")
         )
@@ -333,27 +490,31 @@ class CornersUI:
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         self.layout.addWidget(separator_line)
 
-        self.points_label = QtWidgets.QLabel('<b>%s:</b>' % _('Locations'))
+        self.points_label = FCLabel('<b>%s:</b>' % _('Locations'))
         self.points_label.setToolTip(
             _("Locations where to place corner markers.")
         )
         self.layout.addWidget(self.points_label)
 
-        # BOTTOM LEFT
-        self.bl_cb = FCCheckBox(_("Bottom Left"))
-        self.layout.addWidget(self.bl_cb)
-
-        # BOTTOM RIGHT
-        self.br_cb = FCCheckBox(_("Bottom Right"))
-        self.layout.addWidget(self.br_cb)
+        # ## Grid Layout
+        grid_loc = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid_loc)
 
         # TOP LEFT
         self.tl_cb = FCCheckBox(_("Top Left"))
-        self.layout.addWidget(self.tl_cb)
+        grid_loc.addWidget(self.tl_cb, 0, 0)
 
         # TOP RIGHT
         self.tr_cb = FCCheckBox(_("Top Right"))
-        self.layout.addWidget(self.tr_cb)
+        grid_loc.addWidget(self.tr_cb, 0, 1)
+
+        # BOTTOM LEFT
+        self.bl_cb = FCCheckBox(_("Bottom Left"))
+        grid_loc.addWidget(self.bl_cb, 1, 0)
+
+        # BOTTOM RIGHT
+        self.br_cb = FCCheckBox(_("Bottom Right"))
+        grid_loc.addWidget(self.br_cb, 1, 1)
 
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
@@ -375,59 +536,74 @@ class CornersUI:
         grid_lay.setColumnStretch(0, 0)
         grid_lay.setColumnStretch(1, 1)
 
-        self.param_label = QtWidgets.QLabel('<b>%s:</b>' % _('Parameters'))
+        self.param_label = FCLabel('<b>%s:</b>' % _('Parameters'))
         self.param_label.setToolTip(
             _("Parameters used for this tool.")
         )
         grid_lay.addWidget(self.param_label, 0, 0, 1, 2)
 
+        # Type of Marker
+        self.type_label = FCLabel('%s:' % _("Type"))
+        self.type_label.setToolTip(
+            _("Shape of the marker.")
+        )
+
+        self.type_radio = RadioSet([
+            {"label": _("Semi-Cross"), "value": "s"},
+            {"label": _("Cross"), "value": "c"},
+        ])
+
+        grid_lay.addWidget(self.type_label, 2, 0)
+        grid_lay.addWidget(self.type_radio, 2, 1)
+
         # Thickness #
-        self.thick_label = QtWidgets.QLabel('%s:' % _("Thickness"))
+        self.thick_label = FCLabel('%s:' % _("Thickness"))
         self.thick_label.setToolTip(
             _("The thickness of the line that makes the corner marker.")
         )
         self.thick_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.thick_entry.set_range(0.0000, 9.9999)
+        self.thick_entry.set_range(0.0000, 10.0000)
         self.thick_entry.set_precision(self.decimals)
         self.thick_entry.setWrapping(True)
         self.thick_entry.setSingleStep(10 ** -self.decimals)
 
-        grid_lay.addWidget(self.thick_label, 1, 0)
-        grid_lay.addWidget(self.thick_entry, 1, 1)
+        grid_lay.addWidget(self.thick_label, 4, 0)
+        grid_lay.addWidget(self.thick_entry, 4, 1)
 
         # Length #
-        self.l_label = QtWidgets.QLabel('%s:' % _("Length"))
+        self.l_label = FCLabel('%s:' % _("Length"))
         self.l_label.setToolTip(
             _("The length of the line that makes the corner marker.")
         )
         self.l_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.l_entry.set_range(-9999.9999, 9999.9999)
+        self.l_entry.set_range(-10000.0000, 10000.0000)
         self.l_entry.set_precision(self.decimals)
         self.l_entry.setSingleStep(10 ** -self.decimals)
 
-        grid_lay.addWidget(self.l_label, 2, 0)
-        grid_lay.addWidget(self.l_entry, 2, 1)
+        grid_lay.addWidget(self.l_label, 6, 0)
+        grid_lay.addWidget(self.l_entry, 6, 1)
 
         # Margin #
-        self.margin_label = QtWidgets.QLabel('%s:' % _("Margin"))
+        self.margin_label = FCLabel('%s:' % _("Margin"))
         self.margin_label.setToolTip(
             _("Bounding box margin.")
         )
         self.margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.margin_entry.set_range(-9999.9999, 9999.9999)
+        self.margin_entry.set_range(-10000.0000, 10000.0000)
         self.margin_entry.set_precision(self.decimals)
         self.margin_entry.setSingleStep(0.1)
 
-        grid_lay.addWidget(self.margin_label, 3, 0)
-        grid_lay.addWidget(self.margin_entry, 3, 1)
+        grid_lay.addWidget(self.margin_label, 8, 0)
+        grid_lay.addWidget(self.margin_entry, 8, 1)
 
-        separator_line_2 = QtWidgets.QFrame()
-        separator_line_2.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line_2.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid_lay.addWidget(separator_line_2, 4, 0, 1, 2)
+        # separator_line_2 = QtWidgets.QFrame()
+        # separator_line_2.setFrameShape(QtWidgets.QFrame.HLine)
+        # separator_line_2.setFrameShadow(QtWidgets.QFrame.Sunken)
+        # grid_lay.addWidget(separator_line_2, 10, 0, 1, 2)
 
         # ## Insert Corner Marker
         self.add_marker_button = FCButton(_("Add Marker"))
+        self.add_marker_button.setIcon(QtGui.QIcon(self.app.resource_location + '/corners_32.png'))
         self.add_marker_button.setToolTip(
             _("Will add corner markers to the selected Gerber file.")
         )
@@ -437,7 +613,43 @@ class CornersUI:
                                     font-weight: bold;
                                 }
                                 """)
-        grid_lay.addWidget(self.add_marker_button, 11, 0, 1, 2)
+        grid_lay.addWidget(self.add_marker_button, 12, 0, 1, 2)
+
+        separator_line_2 = QtWidgets.QFrame()
+        separator_line_2.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line_2.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid_lay.addWidget(separator_line_2, 14, 0, 1, 2)
+
+        # Drill is corners
+        self.drills_label = FCLabel('<b>%s:</b>' % _('Drills in Corners'))
+        grid_lay.addWidget(self.drills_label, 16, 0, 1, 2)
+
+        # Drill Tooldia #
+        self.drill_dia_label = FCLabel('%s:' % _("Drill Dia"))
+        self.drill_dia_label.setToolTip(
+            '%s.' % _("Drill Diameter")
+        )
+        self.drill_dia_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.drill_dia_entry.set_range(0.0000, 100.0000)
+        self.drill_dia_entry.set_precision(self.decimals)
+        self.drill_dia_entry.setWrapping(True)
+
+        grid_lay.addWidget(self.drill_dia_label, 18, 0)
+        grid_lay.addWidget(self.drill_dia_entry, 18, 1)
+
+        # ## Create an Excellon object
+        self.drill_button = FCButton(_("Create Excellon Object"))
+        self.drill_button.setIcon(QtGui.QIcon(self.app.resource_location + '/drill32.png'))
+        self.drill_button.setToolTip(
+            _("Will add drill holes in the center of the markers.")
+        )
+        self.drill_button.setStyleSheet("""
+                                        QPushButton
+                                        {
+                                            font-weight: bold;
+                                        }
+                                        """)
+        grid_lay.addWidget(self.drill_button, 20, 0, 1, 2)
 
         self.layout.addStretch()
 
@@ -473,4 +685,4 @@ class CornersUI:
             self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
                                             (_("Edited value is out of range"), minval, maxval), False)
         else:
-            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
+            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)

+ 56 - 43
appTools/ToolCutOut.py

@@ -266,7 +266,7 @@ class CutOut(AppTool):
     def on_tool_add(self, custom_dia=None):
         self.blockSignals(True)
 
-        filename = self.app.data_path + '\\tools_db.FlatDB'
+        filename = self.app.tools_database_path()
 
         new_tools_dict = deepcopy(self.default_data)
         updated_tooldia = None
@@ -311,7 +311,7 @@ class CutOut(AppTool):
 
         offset = 'Path'
         offset_val = 0.0
-        typ = "Rough"
+        typ = 'Rough'
         tool_type = 'V'
         # look in database tools
         for db_tool, db_tool_val in tools_db_dict.items():
@@ -389,7 +389,7 @@ class CutOut(AppTool):
 
     def on_tool_default_add(self, dia=None, muted=None):
 
-        dia = dia
+        dia = dia if dia else str(self.app.defaults["tools_cutout_tooldia"])
         self.default_data.update({
             "plot": True,
 
@@ -442,10 +442,10 @@ class CutOut(AppTool):
         })
 
         self.cut_tool_dict.update({
-            'tooldia': str(self.app.defaults["tools_cutout_tooldia"]),
+            'tooldia': dia,
             'offset': 'Path',
             'offset_value': 0.0,
-            'type': _('Rough'),
+            'type': 'Rough',
             'tool_type': 'C1',
             'data': deepcopy(self.default_data),
             'solid_geometry': []
@@ -462,7 +462,12 @@ class CutOut(AppTool):
         :return:
         """
 
-        if tool['data']['tool_target'] != _("Cutout"):
+        if tool['data']['tool_target'] not in [0, 6]:   # [General, Cutout Tool]
+            for idx in range(self.app.ui.plot_tab_area.count()):
+                if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
+                    wdg = self.app.ui.plot_tab_area.widget(idx)
+                    wdg.deleteLater()
+                    self.app.ui.plot_tab_area.removeTab(idx)
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("Selected tool can't be used here. Pick another."))
             return
         tool_from_db = deepcopy(self.default_data)
@@ -525,7 +530,9 @@ class CutOut(AppTool):
             if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
                 self.app.ui.plot_tab_area.setCurrentWidget(self.app.tools_db_tab)
                 break
-        self.app.on_tools_database(source='cutout')
+        ret_val = self.app.on_tools_database(source='cutout')
+        if ret_val == 'fail':
+            return
         self.app.tools_db_tab.ok_to_add = True
         self.app.tools_db_tab.ui.buttons_frame.hide()
         self.app.tools_db_tab.ui.add_tool_from_db.show()
@@ -570,8 +577,8 @@ class CutOut(AppTool):
 
         if gaps not in ['None', 'LR', 'TB', '2LR', '2TB', '4', '8']:
             self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                 _("Gaps value can be only one of: 'None', 'lr', 'tb', '2lr', '2tb', 4 or 8. "
-                                   "Fill in a correct value and retry. "))
+                                 _("Gaps value can be only one of: 'None', 'lr', 'tb', '2lr', '2tb', 4 or 8.\n"
+                                   "Fill in a correct value and retry."))
             return
 
         # if cutout_obj.multigeo is True:
@@ -867,16 +874,18 @@ class CutOut(AppTool):
                     if not holes:
                         return 'fail'
 
-                    tools = {}
-                    tools[1] = {}
-                    tools[1]["tooldia"] = mb_dia
-                    tools[1]['drills'] = holes
-                    tools[1]['solid_geometry'] = []
+                    tools = {
+                        1: {
+                            "tooldia": mb_dia,
+                            "drills": holes,
+                            "solid_geometry": []
+                        }
+                    }
 
                     exc_obj.tools = tools
                     exc_obj.create_geometry()
                     exc_obj.source_file = app_o.f_handlers.export_excellon(obj_name=exc_obj.options['name'],
-                                                                           local_use=exc_obj,filename=None,
+                                                                           local_use=exc_obj, filename=None,
                                                                            use_thread=False)
                     # calculate the bounds
                     xmin, ymin, xmax, ymax = CutOut.recursive_bounds(exc_obj.solid_geometry)
@@ -897,7 +906,7 @@ class CutOut(AppTool):
                         return
 
                     # cutout_obj.plot(plot_tool=1)
-                    app_obj.inform.emit('[success] %s' % _("Any form CutOut operation finished."))
+                    app_obj.inform.emit('[success] %s' % _("Any-form Cutout operation finished."))
                     # self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
                     app_obj.should_we_save = True
                 except Exception as ee:
@@ -942,9 +951,9 @@ class CutOut(AppTool):
             return
 
         if gaps not in ['None', 'LR', 'TB', '2LR', '2TB', '4', '8']:
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Gaps value can be only one of: "
-                                                          "'None', 'lr', 'tb', '2lr', '2tb', 4 or 8. "
-                                                          "Fill in a correct value and retry. "))
+            msg = '[WARNING_NOTCL] %s' % _("Gaps value can be only one of: 'None', 'lr', 'tb', '2lr', '2tb', 4 or 8.\n"
+                                           "Fill in a correct value and retry.")
+            self.app.inform.emit(msg)
             return
 
         # if cutout_obj.multigeo is True:
@@ -1237,11 +1246,13 @@ class CutOut(AppTool):
                     if not holes:
                         return 'fail'
 
-                    tools = {}
-                    tools[1] = {}
-                    tools[1]["tooldia"] = mb_dia
-                    tools[1]['drills'] = holes
-                    tools[1]['solid_geometry'] = []
+                    tools = {
+                        1: {
+                            "tooldia": mb_dia,
+                            "drills": holes,
+                            "solid_geometry": []
+                        }
+                    }
 
                     exc_obj.tools = tools
                     exc_obj.create_geometry()
@@ -1284,7 +1295,7 @@ class CutOut(AppTool):
             self.man_cutout_obj = self.app.collection.get_by_name(str(name))
         except Exception as e:
             log.debug("CutOut.on_manual_cutout() --> %s" % str(e))
-            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve Geometry object"), name))
+            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), name))
             return
 
         if self.man_cutout_obj is None:
@@ -1316,7 +1327,7 @@ class CutOut(AppTool):
             self.man_cutout_obj = self.app.collection.get_by_name(str(name))
         except Exception as e:
             log.debug("CutOut.on_manual_cutout() --> %s" % str(e))
-            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve Geometry object"), name))
+            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), name))
             return
 
         if self.app.is_legacy is False:
@@ -1413,7 +1424,7 @@ class CutOut(AppTool):
             cutout_obj = self.app.collection.get_by_name(str(name))
         except Exception as e:
             log.debug("CutOut.on_manual_geo() --> %s" % str(e))
-            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve Gerber object"), name))
+            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), name))
             return "Could not retrieve object: %s" % name
 
         if cutout_obj is None:
@@ -1459,7 +1470,7 @@ class CutOut(AppTool):
                     geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
                 else:
                     app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (
-                        _("Geometry not supported for cutout"), type(geo_union)))
+                        _("Geometry not supported"), type(geo_union)))
                     return 'fail'
             else:
                 geo = geo_union
@@ -1595,11 +1606,13 @@ class CutOut(AppTool):
                             if not holes:
                                 return 'fail'
 
-                            tools = {}
-                            tools[1] = {}
-                            tools[1]["tooldia"] = mb_dia
-                            tools[1]['drills'] = holes
-                            tools[1]['solid_geometry'] = []
+                            tools = {
+                                1: {
+                                    "tooldia": mb_dia,
+                                    "drills": holes,
+                                    "solid_geometry": []
+                                }
+                            }
 
                             exc_obj.tools = tools
                             exc_obj.create_geometry()
@@ -1996,9 +2009,9 @@ class CutoutUI:
         # Object kind
         self.kindlabel = QtWidgets.QLabel('%s:' % _('Kind'))
         self.kindlabel.setToolTip(
-            _("Choice of what kind the object we want to cutout is.<BR>"
-              "- <B>Single</B>: contain a single PCB Gerber outline object.<BR>"
-              "- <B>Panel</B>: a panel PCB Gerber object, which is made\n"
+            _("Choice of what kind the object we want to cutout is.\n"
+              "- Single: contain a single PCB Gerber outline object.\n"
+              "- Panel: a panel PCB Gerber object, which is made\n"
               "out of many individual PCB outlines.")
         )
         self.obj_kind_combo = RadioSet([
@@ -2059,7 +2072,7 @@ class CutoutUI:
         # Tool Diameter
         self.dia = FCDoubleSpinner(callback=self.confirmation_message)
         self.dia.set_precision(self.decimals)
-        self.dia.set_range(0.0000, 9999.9999)
+        self.dia.set_range(0.0000, 10000.0000)
 
         self.dia_label = QtWidgets.QLabel('%s:' % _("Tool Dia"))
         self.dia_label.setToolTip(
@@ -2116,9 +2129,9 @@ class CutoutUI:
         self.cutz_entry.set_precision(self.decimals)
 
         if machinist_setting == 0:
-            self.cutz_entry.setRange(-9999.9999, -0.00001)
+            self.cutz_entry.setRange(-10000.0000, -0.00001)
         else:
-            self.cutz_entry.setRange(-9999.9999, 9999.9999)
+            self.cutz_entry.setRange(-10000.0000, 10000.0000)
 
         self.cutz_entry.setSingleStep(0.1)
 
@@ -2138,7 +2151,7 @@ class CutoutUI:
 
         self.maxdepth_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.maxdepth_entry.set_precision(self.decimals)
-        self.maxdepth_entry.setRange(0, 9999.9999)
+        self.maxdepth_entry.setRange(0, 10000.0000)
         self.maxdepth_entry.setSingleStep(0.1)
 
         self.maxdepth_entry.setToolTip(
@@ -2154,7 +2167,7 @@ class CutoutUI:
 
         # Margin
         self.margin = FCDoubleSpinner(callback=self.confirmation_message)
-        self.margin.set_range(-9999.9999, 9999.9999)
+        self.margin.set_range(-10000.0000, 10000.0000)
         self.margin.setSingleStep(0.1)
         self.margin.set_precision(self.decimals)
 
@@ -2212,9 +2225,9 @@ class CutoutUI:
         self.thin_depth_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.thin_depth_entry.set_precision(self.decimals)
         if machinist_setting == 0:
-            self.thin_depth_entry.setRange(-9999.9999, -0.00001)
+            self.thin_depth_entry.setRange(-10000.0000, -0.00001)
         else:
-            self.thin_depth_entry.setRange(-9999.9999, 9999.9999)
+            self.thin_depth_entry.setRange(-10000.0000, 10000.0000)
         self.thin_depth_entry.setSingleStep(0.1)
 
         grid0.addWidget(self.thin_depth_label, 32, 0)

+ 40 - 34
appTools/ToolDblSided.py

@@ -2,7 +2,7 @@
 from PyQt5 import QtWidgets, QtCore, QtGui
 
 from appTool import AppTool
-from appGUI.GUIElements import RadioSet, FCDoubleSpinner, FCButton, FCComboBox, NumericalEvalTupleEntry
+from appGUI.GUIElements import RadioSet, FCDoubleSpinner, FCButton, FCComboBox, NumericalEvalTupleEntry, FCLabel
 
 from numpy import Inf
 
@@ -182,9 +182,9 @@ class DblSidedTool(AppTool):
             self.app.inform.emit(msg)
             return
 
-        tools = {}
-        tools[1] = {}
+        tools = {1: {}}
         tools[1]["tooldia"] = dia
+        tools[1]['drills'] = []
         tools[1]['solid_geometry'] = []
 
         # holes = self.alignment_holes.get_value()
@@ -198,9 +198,8 @@ class DblSidedTool(AppTool):
             point = Point(hole)
             point_mirror = affinity.scale(point, xscale, yscale, origin=(px, py))
 
-            tools[1]['drills'] = [point, point_mirror]
-            tools[1]['solid_geometry'].append(point)
-            tools[1]['solid_geometry'].append(point_mirror)
+            tools[1]['drills'] += [point, point_mirror]
+            tools[1]['solid_geometry'] += [point, point_mirror]
 
         def obj_init(obj_inst, app_inst):
             obj_inst.tools = tools
@@ -210,9 +209,11 @@ class DblSidedTool(AppTool):
                                                                        filename=None,
                                                                        use_thread=False)
 
-        self.app.app_obj.new_object("excellon", "Alignment Drills", obj_init)
+        ret_val = self.app.app_obj.new_object("excellon", _("Alignment Drills"), obj_init)
         self.drill_values = ''
-        self.app.inform.emit('[success] %s' % _("Excellon object with alignment drills created..."))
+
+        if not ret_val == 'fail':
+            self.app.inform.emit('[success] %s' % _("Excellon object with alignment drills created..."))
 
     def on_pick_hole(self):
 
@@ -402,7 +403,7 @@ class DblSidedTool(AppTool):
         obj_list = self.app.collection.get_selected()
 
         if not obj_list:
-            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed. No object(s) selected..."))
+            self.app.inform.emit('[ERROR_NOTCL] %s %s' % (_("Failed."), _("No object is selected.")))
             return
 
         for obj in obj_list:
@@ -494,7 +495,7 @@ class DsidedUI:
         self.layout = layout
 
         # ## Title
-        title_label = QtWidgets.QLabel("%s" % self.toolName)
+        title_label = FCLabel("%s" % self.toolName)
         title_label.setStyleSheet("""
                                 QLabel
                                 {
@@ -503,7 +504,7 @@ class DsidedUI:
                                 }
                                 """)
         self.layout.addWidget(title_label)
-        self.layout.addWidget(QtWidgets.QLabel(""))
+        self.layout.addWidget(FCLabel(""))
 
         # ## Grid Layout
         grid_lay = QtWidgets.QGridLayout()
@@ -512,13 +513,13 @@ class DsidedUI:
         self.layout.addLayout(grid_lay)
 
         # Objects to be mirrored
-        self.m_objects_label = QtWidgets.QLabel("<b>%s:</b>" % _("Source Object"))
+        self.m_objects_label = FCLabel("<b>%s:</b>" % _("Source Object"))
         self.m_objects_label.setToolTip('%s.' % _("Objects to be mirrored"))
 
         grid_lay.addWidget(self.m_objects_label, 0, 0, 1, 2)
 
         # Type of object to be cutout
-        self.type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Type"))
+        self.type_obj_combo_label = FCLabel('%s:' % _("Type"))
         self.type_obj_combo_label.setToolTip(
             _("Select the type of application object to be processed in this tool.")
         )
@@ -554,7 +555,7 @@ class DsidedUI:
         self.layout.addLayout(grid0)
 
         # ## Title Bounds Values
-        self.bv_label = QtWidgets.QLabel("<b>%s:</b>" % _('Bounds Values'))
+        self.bv_label = FCLabel("<b>%s:</b>" % _('Bounds Values'))
         self.bv_label.setToolTip(
             _("Select on canvas the object(s)\n"
               "for which to calculate bounds values.")
@@ -564,7 +565,7 @@ class DsidedUI:
         # Xmin value
         self.xmin_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.xmin_entry.set_precision(self.decimals)
-        self.xmin_entry.set_range(-9999.9999, 9999.9999)
+        self.xmin_entry.set_range(-10000.0000, 10000.0000)
 
         self.xmin_btn = FCButton('%s:' % _("X min"))
         self.xmin_btn.setToolTip(
@@ -578,7 +579,7 @@ class DsidedUI:
         # Ymin value
         self.ymin_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.ymin_entry.set_precision(self.decimals)
-        self.ymin_entry.set_range(-9999.9999, 9999.9999)
+        self.ymin_entry.set_range(-10000.0000, 10000.0000)
 
         self.ymin_btn = FCButton('%s:' % _("Y min"))
         self.ymin_btn.setToolTip(
@@ -592,7 +593,7 @@ class DsidedUI:
         # Xmax value
         self.xmax_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.xmax_entry.set_precision(self.decimals)
-        self.xmax_entry.set_range(-9999.9999, 9999.9999)
+        self.xmax_entry.set_range(-10000.0000, 10000.0000)
 
         self.xmax_btn = FCButton('%s:' % _("X max"))
         self.xmax_btn.setToolTip(
@@ -606,7 +607,7 @@ class DsidedUI:
         # Ymax value
         self.ymax_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.ymax_entry.set_precision(self.decimals)
-        self.ymax_entry.set_range(-9999.9999, 9999.9999)
+        self.ymax_entry.set_range(-10000.0000, 10000.0000)
 
         self.ymax_btn = FCButton('%s:' % _("Y max"))
         self.ymax_btn.setToolTip(
@@ -632,7 +633,7 @@ class DsidedUI:
         grid0.addWidget(self.center_entry, 12, 1)
 
         # Calculate Bounding box
-        self.calculate_bb_button = QtWidgets.QPushButton(_("Calculate Bounds Values"))
+        self.calculate_bb_button = FCButton(_("Calculate Bounds Values"))
         self.calculate_bb_button.setToolTip(
             _("Calculate the enveloping rectangular shape coordinates,\n"
               "for the selection of objects.\n"
@@ -659,13 +660,13 @@ class DsidedUI:
         grid1.setColumnStretch(1, 1)
         self.layout.addLayout(grid1)
 
-        self.param_label = QtWidgets.QLabel("<b>%s:</b>" % _("Mirror Operation"))
+        self.param_label = FCLabel("<b>%s:</b>" % _("Mirror Operation"))
         self.param_label.setToolTip('%s.' % _("Parameters for the mirror operation"))
 
         grid1.addWidget(self.param_label, 0, 0, 1, 2)
 
         # ## Axis
-        self.mirax_label = QtWidgets.QLabel('%s:' % _("Axis"))
+        self.mirax_label = FCLabel('%s:' % _("Axis"))
         self.mirax_label.setToolTip(_("Mirror vertically (X) or horizontally (Y)."))
         self.mirror_axis = RadioSet(
             [
@@ -680,7 +681,7 @@ class DsidedUI:
         grid1.addWidget(self.mirror_axis, 2, 1, 1, 2)
 
         # ## Axis Location
-        self.axloc_label = QtWidgets.QLabel('%s:' % _("Reference"))
+        self.axloc_label = FCLabel('%s:' % _("Reference"))
         self.axloc_label.setToolTip(
             _("The coordinates used as reference for the mirror operation.\n"
               "Can be:\n"
@@ -705,7 +706,8 @@ class DsidedUI:
         self.point_entry.setPlaceholderText(_("Point coordinates"))
 
         # Add a reference
-        self.add_point_button = QtWidgets.QPushButton(_("Add"))
+        self.add_point_button = FCButton(_("Add"))
+        self.add_point_button.setIcon(QtGui.QIcon(self.app.resource_location + '/plus16.png'))
         self.add_point_button.setToolTip(
             _("Add the coordinates in format <b>(x, y)</b> through which the mirroring axis\n "
               "selected in 'MIRROR AXIS' pass.\n"
@@ -723,7 +725,7 @@ class DsidedUI:
         grid1.addWidget(self.point_entry, 7, 0, 1, 2)
         grid1.addWidget(self.add_point_button, 7, 2)
 
-        self.exc_hole_lbl = QtWidgets.QLabel('%s:' % _("Excellon"))
+        self.exc_hole_lbl = FCLabel('%s:' % _("Excellon"))
         self.exc_hole_lbl.setToolTip(
             _("Object that holds holes that can be picked as reference for mirroring.")
         )
@@ -756,7 +758,7 @@ class DsidedUI:
         grid_lay3.setColumnStretch(1, 1)
         grid1.addLayout(grid_lay3, 14, 0, 1, 3)
 
-        self.box_type_label = QtWidgets.QLabel('%s:' % _("Reference Object"))
+        self.box_type_label = FCLabel('%s:' % _("Reference Object"))
         self.box_type_label.setToolTip(
             _("It can be of type: Gerber or Excellon or Geometry.\n"
               "The coordinates of the center of the bounding box are used\n"
@@ -784,7 +786,8 @@ class DsidedUI:
 
         grid_lay3.addWidget(self.box_combo, 3, 0, 1, 2)
 
-        self.mirror_button = QtWidgets.QPushButton(_("Mirror"))
+        self.mirror_button = FCButton(_("Mirror"))
+        self.mirror_button.setIcon(QtGui.QIcon(self.app.resource_location + '/doubleside16.png'))
         self.mirror_button.setToolTip(
             _("Mirrors (flips) the specified object around \n"
               "the specified axis. Does not create a new \n"
@@ -812,7 +815,7 @@ class DsidedUI:
         self.layout.addLayout(grid4)
 
         # ## Alignment holes
-        self.alignment_label = QtWidgets.QLabel("<b>%s:</b>" % _('PCB Alignment'))
+        self.alignment_label = FCLabel("<b>%s:</b>" % _('PCB Alignment'))
         self.alignment_label.setToolTip(
             _("Creates an Excellon Object containing the\n"
               "specified alignment holes and their mirror\n"
@@ -821,7 +824,7 @@ class DsidedUI:
         grid4.addWidget(self.alignment_label, 0, 0, 1, 2)
 
         # ## Drill diameter for alignment holes
-        self.dt_label = QtWidgets.QLabel("%s:" % _('Drill Diameter'))
+        self.dt_label = FCLabel("%s:" % _('Drill Dia'))
         self.dt_label.setToolTip(
             _("Diameter of the drill for the alignment holes.")
         )
@@ -831,13 +834,13 @@ class DsidedUI:
             _("Diameter of the drill for the alignment holes.")
         )
         self.drill_dia.set_precision(self.decimals)
-        self.drill_dia.set_range(0.0000, 9999.9999)
+        self.drill_dia.set_range(0.0000, 10000.0000)
 
         grid4.addWidget(self.dt_label, 2, 0)
         grid4.addWidget(self.drill_dia, 2, 1)
 
         # ## Alignment Axis
-        self.align_ax_label = QtWidgets.QLabel('%s:' % _("Axis"))
+        self.align_ax_label = FCLabel('%s:' % _("Axis"))
         self.align_ax_label.setToolTip(
             _("Mirror vertically (X) or horizontally (Y).")
         )
@@ -854,7 +857,7 @@ class DsidedUI:
         grid4.addWidget(self.align_axis_radio, 4, 1)
 
         # ## Alignment Reference Point
-        self.align_ref_label = QtWidgets.QLabel('%s:' % _("Reference"))
+        self.align_ref_label = FCLabel('%s:' % _("Reference"))
         self.align_ref_label.setToolTip(
             _("The reference point used to create the second alignment drill\n"
               "from the first alignment drill, by doing mirror.\n"
@@ -876,7 +879,7 @@ class DsidedUI:
         self.layout.addLayout(grid5)
 
         # ## Alignment holes
-        self.ah_label = QtWidgets.QLabel("%s:" % _('Alignment Drill Coordinates'))
+        self.ah_label = FCLabel("%s:" % _('Alignment Drill Coordinates'))
         self.ah_label.setToolTip(
             _("Alignment holes (x1, y1), (x2, y2), ... "
               "on one side of the mirror axis. For each set of (x, y) coordinates\n"
@@ -892,6 +895,7 @@ class DsidedUI:
         grid5.addWidget(self.alignment_holes, 1, 0, 1, 2)
 
         self.add_drill_point_button = FCButton(_("Add"))
+        self.add_drill_point_button.setIcon(QtGui.QIcon(self.app.resource_location + '/plus16.png'))
         self.add_drill_point_button.setToolTip(
             _("Add alignment drill holes coordinates in the format: (x1, y1), (x2, y2), ... \n"
               "on one side of the alignment axis.\n\n"
@@ -909,6 +913,7 @@ class DsidedUI:
         #                 """)
 
         self.delete_drill_point_button = FCButton(_("Delete Last"))
+        self.delete_drill_point_button.setIcon(QtGui.QIcon(self.app.resource_location + '/trash32.png'))
         self.delete_drill_point_button.setToolTip(
             _("Delete the last coordinates tuple in the list.")
         )
@@ -920,7 +925,8 @@ class DsidedUI:
         grid5.addLayout(drill_hlay, 2, 0, 1, 2)
 
         # ## Buttons
-        self.create_alignment_hole_button = QtWidgets.QPushButton(_("Create Excellon Object"))
+        self.create_alignment_hole_button = FCButton(_("Create Excellon Object"))
+        self.create_alignment_hole_button.setIcon(QtGui.QIcon(self.app.resource_location + '/drill32.png'))
         self.create_alignment_hole_button.setToolTip(
             _("Creates an Excellon Object containing the\n"
               "specified alignment holes and their mirror\n"
@@ -937,7 +943,7 @@ class DsidedUI:
         self.layout.addStretch()
 
         # ## Reset Tool
-        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
+        self.reset_button = FCButton(_("Reset Tool"))
         self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png'))
         self.reset_button.setToolTip(
             _("Will reset the tool parameters.")

+ 3 - 2
appTools/ToolDistance.py

@@ -364,7 +364,8 @@ class Distance(AppTool):
                         self.app.inform.emit('[WARNING_NOTCL] %s' % _("Pads overlapped. Aborting."))
                         return
 
-                    pos = (clicked_pads[0].x, clicked_pads[0].y)
+                    if clicked_pads:
+                        pos = (clicked_pads[0].x, clicked_pads[0].y)
 
                 self.app.on_jump_to(custom_location=pos, fit_center=False)
                 # Update cursor
@@ -394,7 +395,7 @@ class Distance(AppTool):
     def calculate_distance(self, pos):
         if len(self.points) == 1:
             self.ui.start_entry.set_value("(%.*f, %.*f)" % (self.decimals, pos[0], self.decimals, pos[1]))
-            self.app.inform.emit(_("MEASURING: Click on the Destination point ..."))
+            self.app.inform.emit(_("Click on the DESTINATION point ..."))
         elif len(self.points) == 2:
             # self.app.app_cursor.enabled = False
             dx = self.points[1][0] - self.points[0][0]

+ 22 - 22
appTools/ToolDrilling.py

@@ -884,7 +884,7 @@ class ToolDrilling(AppTool, Excellon):
 
     def on_tool_db_load(self):
 
-        filename = self.app.data_path + '\\tools_db.FlatDB'
+        filename = self.app.tools_database_path()
 
         # load the database tools from the file
         try:
@@ -1213,7 +1213,7 @@ class ToolDrilling(AppTool, Excellon):
         # if the sender is in the column with index 2 then we update the tool_type key
         if cw_col == 2:
             tt = cw.currentText()
-            typ = 'Iso' if tt == 'V' else "Rough"
+            typ = 'Iso' if tt == 'V' else 'Rough'
 
             self.excellon_tools[current_uid].update({
                 'type': typ,
@@ -1670,7 +1670,7 @@ class ToolDrilling(AppTool, Excellon):
         # Object initialization function for app.app_obj.new_object()
         def job_init(job_obj, app_obj):
             assert job_obj.kind == 'cncjob', "Initializer expected a CNCJobObject, got %s" % type(job_obj)
-            app_obj.inform.emit(_("Generating Excellon CNCJob..."))
+            app_obj.inform.emit(_("Generating CNCJob..."))
 
             # #########################################################################################################
             # #########################################################################################################
@@ -2069,7 +2069,7 @@ class DrillingUI:
               "will be showed as a T1, T2 ... Tn in the Machine Code.\n\n"
               "Here the tools are selected for G-code generation."))
         self.tools_table.horizontalHeaderItem(1).setToolTip(
-            _("Tool Diameter. It's value (in current FlatCAM units) \n"
+            _("Tool Diameter. Its value\n"
               "is the cut width into the material."))
         self.tools_table.horizontalHeaderItem(2).setToolTip(
             _("The number of Drill holes. Holes that are drilled with\n"
@@ -2150,9 +2150,9 @@ class DrillingUI:
         self.cutz_entry.set_precision(self.decimals)
 
         if machinist_setting == 0:
-            self.cutz_entry.set_range(-9999.9999, 0.0000)
+            self.cutz_entry.set_range(-10000.0000, 0.0000)
         else:
-            self.cutz_entry.set_range(-9999.9999, 9999.9999)
+            self.cutz_entry.set_range(-10000.0000, 10000.0000)
 
         self.cutz_entry.setSingleStep(0.1)
         self.cutz_entry.setObjectName("e_cutz")
@@ -2174,7 +2174,7 @@ class DrillingUI:
 
         self.maxdepth_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.maxdepth_entry.set_precision(self.decimals)
-        self.maxdepth_entry.set_range(0, 9999.9999)
+        self.maxdepth_entry.set_range(0, 10000.0000)
         self.maxdepth_entry.setSingleStep(0.1)
 
         self.maxdepth_entry.setToolTip(_("Depth of each pass (positive)."))
@@ -2196,9 +2196,9 @@ class DrillingUI:
         self.travelz_entry.set_precision(self.decimals)
 
         if machinist_setting == 0:
-            self.travelz_entry.set_range(0.00001, 9999.9999)
+            self.travelz_entry.set_range(0.00001, 10000.0000)
         else:
-            self.travelz_entry.set_range(-9999.9999, 9999.9999)
+            self.travelz_entry.set_range(-10000.0000, 10000.0000)
 
         self.travelz_entry.setSingleStep(0.1)
         self.travelz_entry.setObjectName("e_travelz")
@@ -2216,7 +2216,7 @@ class DrillingUI:
         )
         self.feedrate_z_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.feedrate_z_entry.set_precision(self.decimals)
-        self.feedrate_z_entry.set_range(0.0, 99999.9999)
+        self.feedrate_z_entry.set_range(0.0, 910000.0000)
         self.feedrate_z_entry.setSingleStep(0.1)
         self.feedrate_z_entry.setObjectName("e_feedratez")
 
@@ -2234,7 +2234,7 @@ class DrillingUI:
         )
         self.feedrate_rapid_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.feedrate_rapid_entry.set_precision(self.decimals)
-        self.feedrate_rapid_entry.set_range(0.0, 99999.9999)
+        self.feedrate_rapid_entry.set_range(0.0, 910000.0000)
         self.feedrate_rapid_entry.setSingleStep(0.1)
         self.feedrate_rapid_entry.setObjectName("e_fr_rapid")
 
@@ -2271,7 +2271,7 @@ class DrillingUI:
         # Dwelltime
         self.dwelltime_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.dwelltime_entry.set_precision(self.decimals)
-        self.dwelltime_entry.set_range(0.0, 9999.9999)
+        self.dwelltime_entry.set_range(0.0, 10000.0000)
         self.dwelltime_entry.setSingleStep(0.1)
 
         self.dwelltime_entry.setToolTip(
@@ -2294,7 +2294,7 @@ class DrillingUI:
 
         self.offset_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.offset_entry.set_precision(self.decimals)
-        self.offset_entry.set_range(-9999.9999, 9999.9999)
+        self.offset_entry.set_range(-10000.0000, 10000.0000)
         self.offset_entry.setObjectName("e_offset")
 
         self.grid1.addWidget(self.tool_offset_label, 25, 0)
@@ -2402,9 +2402,9 @@ class DrillingUI:
         self.toolchangez_entry.setObjectName("e_toolchangez")
 
         if machinist_setting == 0:
-            self.toolchangez_entry.set_range(0.0, 9999.9999)
+            self.toolchangez_entry.set_range(0.0, 10000.0000)
         else:
-            self.toolchangez_entry.set_range(-9999.9999, 9999.9999)
+            self.toolchangez_entry.set_range(-10000.0000, 10000.0000)
 
         self.toolchangez_entry.setSingleStep(0.1)
 
@@ -2414,7 +2414,7 @@ class DrillingUI:
         # Start move Z:
         self.estartz_label = QtWidgets.QLabel('%s:' % _("Start Z"))
         self.estartz_label.setToolTip(
-            _("Height of the tool just after start.\n"
+            _("Height of the tool just after starting the work.\n"
               "Delete the value if you don't need this feature.")
         )
         self.estartz_entry = NumericalEvalEntry(border_color='#0069A9')
@@ -2434,9 +2434,9 @@ class DrillingUI:
         self.endz_entry.setObjectName("e_endz")
 
         if machinist_setting == 0:
-            self.endz_entry.set_range(0.0, 9999.9999)
+            self.endz_entry.set_range(0.0, 10000.0000)
         else:
-            self.endz_entry.set_range(-9999.9999, 9999.9999)
+            self.endz_entry.set_range(-10000.0000, 10000.0000)
 
         self.endz_entry.setSingleStep(0.1)
 
@@ -2466,7 +2466,7 @@ class DrillingUI:
 
         self.pdepth_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.pdepth_entry.set_precision(self.decimals)
-        self.pdepth_entry.set_range(-9999.9999, 9999.9999)
+        self.pdepth_entry.set_range(-10000.0000, 10000.0000)
         self.pdepth_entry.setSingleStep(0.1)
         self.pdepth_entry.setObjectName("e_depth_probe")
 
@@ -2484,7 +2484,7 @@ class DrillingUI:
 
         self.feedrate_probe_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.feedrate_probe_entry.set_precision(self.decimals)
-        self.feedrate_probe_entry.set_range(0.0, 9999.9999)
+        self.feedrate_probe_entry.set_range(0.0, 10000.0000)
         self.feedrate_probe_entry.setSingleStep(0.1)
         self.feedrate_probe_entry.setObjectName("e_fr_probe")
 
@@ -2573,7 +2573,7 @@ class DrillingUI:
         self.over_z_label.setToolTip(_("The height Z to which the tool will rise in order to avoid\n"
                                        "an interdiction area."))
         self.over_z_entry = FCDoubleSpinner()
-        self.over_z_entry.set_range(0.000, 9999.9999)
+        self.over_z_entry.set_range(0.000, 10000.0000)
         self.over_z_entry.set_precision(self.decimals)
         self.over_z_entry.setObjectName("e_area_overz")
 
@@ -2581,7 +2581,7 @@ class DrillingUI:
         grid_a1.addWidget(self.over_z_entry, 2, 1)
 
         # Button Add Area
-        self.add_area_button = QtWidgets.QPushButton(_('Add area:'))
+        self.add_area_button = QtWidgets.QPushButton(_('Add Area:'))
         self.add_area_button.setToolTip(_("Add an Exclusion Area."))
 
         # Area Selection shape

+ 10 - 13
appTools/ToolEtchCompensation.py

@@ -190,9 +190,9 @@ class ToolEtchCompensation(AppTool):
 
         # update the apertures attributes (keys in the apertures dict)
         for ap in new_apertures:
-            type = new_apertures[ap]['type']
+            ap_type = new_apertures[ap]['type']
             for k in new_apertures[ap]:
-                if type == 'R' or type == 'O':
+                if ap_type == 'R' or ap_type == 'O':
                     if k == 'width' or k == 'height':
                         new_apertures[ap][k] += offset
                 else:
@@ -207,9 +207,9 @@ class ToolEtchCompensation(AppTool):
         # in case of 'R' or 'O' aperture type we need to update the aperture 'size' after
         # the 'width' and 'height' keys were updated
         for ap in new_apertures:
-            type = new_apertures[ap]['type']
+            ap_type = new_apertures[ap]['type']
             for k in new_apertures[ap]:
-                if type == 'R' or type == 'O':
+                if ap_type == 'R' or ap_type == 'O':
                     if k == 'size':
                         new_apertures[ap][k] = math.sqrt(
                             new_apertures[ap]['width'] ** 2 + new_apertures[ap]['height'] ** 2)
@@ -233,8 +233,8 @@ class ToolEtchCompensation(AppTool):
             new_obj.apertures = deepcopy(new_apertures)
 
             new_obj.solid_geometry = deepcopy(new_solid_geometry)
-            new_obj.source_file = self.app.f_handlers.export_gerber(obj_name=outname, filename=None, local_use=new_obj,
-                                                                    use_thread=False)
+            new_obj.source_file = app_obj.f_handlers.export_gerber(obj_name=outname, filename=None, local_use=new_obj,
+                                                                   use_thread=False)
 
         self.app.app_obj.new_object('gerber', outname, init_func)
 
@@ -366,8 +366,7 @@ class EtchUI:
         )
         self.thick_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.thick_entry.set_precision(self.decimals)
-        self.thick_entry.set_range(0.0000, 9999.9999)
-        self.thick_entry.setObjectName(_("Thickness"))
+        self.thick_entry.set_range(0.0000, 10000.0000)
 
         grid0.addWidget(self.thick_label, 12, 0)
         grid0.addWidget(self.thick_entry, 12, 1)
@@ -394,21 +393,19 @@ class EtchUI:
             _("A list of etchants.")
         )
         self.etchants_combo = FCComboBox(callback=self.confirmation_message)
-        self.etchants_combo.setObjectName(_("Etchants"))
         self.etchants_combo.addItems(["CuCl2", "Fe3Cl", _("Alkaline baths")])
 
         grid0.addWidget(self.etchants_label, 18, 0)
         grid0.addWidget(self.etchants_combo, 18, 1)
 
         # Etch Factor
-        self.factor_label = QtWidgets.QLabel('%s:' % _('Etch factor'))
+        self.factor_label = QtWidgets.QLabel('%s:' % _('Etch Factor'))
         self.factor_label.setToolTip(
             _("The ratio between depth etch and lateral etch .\n"
               "Accepts real numbers and formulas using the operators: /,*,+,-,%")
         )
         self.factor_entry = NumericalEvalEntry(border_color='#0069A9')
         self.factor_entry.setPlaceholderText(_("Real number or formula"))
-        self.factor_entry.setObjectName(_("Etch_factor"))
 
         grid0.addWidget(self.factor_label, 19, 0)
         grid0.addWidget(self.factor_entry, 19, 1)
@@ -421,8 +418,7 @@ class EtchUI:
         )
         self.offset_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.offset_entry.set_precision(self.decimals)
-        self.offset_entry.set_range(-9999.9999, 9999.9999)
-        self.offset_entry.setObjectName(_("Offset"))
+        self.offset_entry.set_range(-10000.0000, 10000.0000)
 
         grid0.addWidget(self.offset_label, 20, 0)
         grid0.addWidget(self.offset_entry, 20, 1)
@@ -441,6 +437,7 @@ class EtchUI:
         grid0.addWidget(separator_line, 22, 0, 1, 2)
 
         self.compensate_btn = FCButton(_('Compensate'))
+        self.compensate_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/etch_32.png'))
         self.compensate_btn.setToolTip(
             _("Will increase the copper features thickness to compensate the lateral etch.")
         )

+ 40 - 44
appTools/ToolExtractDrills.py

@@ -381,35 +381,35 @@ class ToolExtractDrills(AppTool):
 
     def on_hole_size_toggle(self, val):
         if val == "fixed":
-            self.ui.fixed_label.setDisabled(False)
-            self.ui.dia_entry.setDisabled(False)
-            self.ui.dia_label.setDisabled(False)
+            self.ui.fixed_label.setVisible(True)
+            self.ui.dia_entry.setVisible(True)
+            self.ui.dia_label.setVisible(True)
 
-            self.ui.ring_frame.setDisabled(True)
+            self.ui.ring_frame.setVisible(False)
 
-            self.ui.prop_label.setDisabled(True)
-            self.ui.factor_label.setDisabled(True)
-            self.ui.factor_entry.setDisabled(True)
+            self.ui.prop_label.setVisible(False)
+            self.ui.factor_label.setVisible(False)
+            self.ui.factor_entry.setVisible(False)
         elif val == "ring":
-            self.ui.fixed_label.setDisabled(True)
-            self.ui.dia_entry.setDisabled(True)
-            self.ui.dia_label.setDisabled(True)
+            self.ui.fixed_label.setVisible(False)
+            self.ui.dia_entry.setVisible(False)
+            self.ui.dia_label.setVisible(False)
 
-            self.ui.ring_frame.setDisabled(False)
+            self.ui.ring_frame.setVisible(True)
 
-            self.ui.prop_label.setDisabled(True)
-            self.ui.factor_label.setDisabled(True)
-            self.ui.factor_entry.setDisabled(True)
+            self.ui.prop_label.setVisible(False)
+            self.ui.factor_label.setVisible(False)
+            self.ui.factor_entry.setVisible(False)
         elif val == "prop":
-            self.ui.fixed_label.setDisabled(True)
-            self.ui.dia_entry.setDisabled(True)
-            self.ui.dia_label.setDisabled(True)
+            self.ui.fixed_label.setVisible(False)
+            self.ui.dia_entry.setVisible(False)
+            self.ui.dia_label.setVisible(False)
 
-            self.ui.ring_frame.setDisabled(True)
+            self.ui.ring_frame.setVisible(False)
 
-            self.ui.prop_label.setDisabled(False)
-            self.ui.factor_label.setDisabled(False)
-            self.ui.factor_entry.setDisabled(False)
+            self.ui.prop_label.setVisible(True)
+            self.ui.factor_label.setVisible(True)
+            self.ui.factor_entry.setVisible(True)
 
     def reset_fields(self):
         self.ui.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
@@ -530,8 +530,8 @@ class ExtractDrillsUI:
         self.hole_size_radio = RadioSet(
             [
                 {'label': _("Fixed Diameter"), 'value': 'fixed'},
-                {'label': _("Fixed Annular Ring"), 'value': 'ring'},
-                {'label': _("Proportional"), 'value': 'prop'}
+                {'label': _("Proportional"), 'value': 'prop'},
+                {'label': _("Fixed Annular Ring"), 'value': 'ring'}
             ],
             orientation='vertical',
             stretch=False)
@@ -552,7 +552,7 @@ class ExtractDrillsUI:
         # Diameter value
         self.dia_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.dia_entry.set_precision(self.decimals)
-        self.dia_entry.set_range(0.0000, 9999.9999)
+        self.dia_entry.set_range(0.0000, 10000.0000)
 
         self.dia_label = QtWidgets.QLabel('%s:' % _("Value"))
         self.dia_label.setToolTip(
@@ -562,11 +562,6 @@ class ExtractDrillsUI:
         grid1.addWidget(self.dia_label, 8, 0)
         grid1.addWidget(self.dia_entry, 8, 1)
 
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid1.addWidget(separator_line, 9, 0, 1, 2)
-
         self.ring_frame = QtWidgets.QFrame()
         self.ring_frame.setContentsMargins(0, 0, 0, 0)
         self.layout.addWidget(self.ring_frame)
@@ -598,7 +593,7 @@ class ExtractDrillsUI:
 
         self.circular_ring_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.circular_ring_entry.set_precision(self.decimals)
-        self.circular_ring_entry.set_range(0.0000, 9999.9999)
+        self.circular_ring_entry.set_range(0.0000, 10000.0000)
 
         grid2.addWidget(self.circular_ring_label, 1, 0)
         grid2.addWidget(self.circular_ring_entry, 1, 1)
@@ -611,7 +606,7 @@ class ExtractDrillsUI:
 
         self.oblong_ring_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.oblong_ring_entry.set_precision(self.decimals)
-        self.oblong_ring_entry.set_range(0.0000, 9999.9999)
+        self.oblong_ring_entry.set_range(0.0000, 10000.0000)
 
         grid2.addWidget(self.oblong_ring_label, 2, 0)
         grid2.addWidget(self.oblong_ring_entry, 2, 1)
@@ -624,7 +619,7 @@ class ExtractDrillsUI:
 
         self.square_ring_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.square_ring_entry.set_precision(self.decimals)
-        self.square_ring_entry.set_range(0.0000, 9999.9999)
+        self.square_ring_entry.set_range(0.0000, 10000.0000)
 
         grid2.addWidget(self.square_ring_label, 3, 0)
         grid2.addWidget(self.square_ring_entry, 3, 1)
@@ -637,7 +632,7 @@ class ExtractDrillsUI:
 
         self.rectangular_ring_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.rectangular_ring_entry.set_precision(self.decimals)
-        self.rectangular_ring_entry.set_range(0.0000, 9999.9999)
+        self.rectangular_ring_entry.set_range(0.0000, 10000.0000)
 
         grid2.addWidget(self.rectangular_ring_label, 4, 0)
         grid2.addWidget(self.rectangular_ring_entry, 4, 1)
@@ -650,7 +645,7 @@ class ExtractDrillsUI:
 
         self.other_ring_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.other_ring_entry.set_precision(self.decimals)
-        self.other_ring_entry.set_range(0.0000, 9999.9999)
+        self.other_ring_entry.set_range(0.0000, 10000.0000)
 
         grid2.addWidget(self.other_ring_label, 5, 0)
         grid2.addWidget(self.other_ring_entry, 5, 1)
@@ -660,11 +655,6 @@ class ExtractDrillsUI:
         grid3.setColumnStretch(0, 0)
         grid3.setColumnStretch(1, 1)
 
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid3.addWidget(separator_line, 1, 0, 1, 2)
-
         # Annular Ring value
         self.prop_label = QtWidgets.QLabel('<b>%s</b>' % _("Proportional Diameter"))
         grid3.addWidget(self.prop_label, 2, 0, 1, 2)
@@ -684,8 +674,14 @@ class ExtractDrillsUI:
         grid3.addWidget(self.factor_label, 3, 0)
         grid3.addWidget(self.factor_entry, 3, 1)
 
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid3.addWidget(separator_line, 5, 0, 1, 2)
+
         # Extract drills from Gerber apertures flashes (pads)
         self.e_drills_button = QtWidgets.QPushButton(_("Extract Drills"))
+        self.e_drills_button.setIcon(QtGui.QIcon(self.app.resource_location + '/drill16.png'))
         self.e_drills_button.setToolTip(
             _("Extract drills from a given Gerber file.")
         )
@@ -719,12 +715,12 @@ class ExtractDrillsUI:
         self.rectangular_ring_entry.setEnabled(False)
         self.other_ring_entry.setEnabled(False)
 
-        self.dia_entry.setDisabled(True)
-        self.dia_label.setDisabled(True)
-        self.factor_label.setDisabled(True)
-        self.factor_entry.setDisabled(True)
+        self.dia_entry.setVisible(False)
+        self.dia_label.setVisible(False)
+        self.factor_label.setVisible(False)
+        self.factor_entry.setVisible(False)
 
-        self.ring_frame.setDisabled(True)
+        self.ring_frame.setVisible(False)
         # #################################### FINSIHED GUI ###########################
         # #############################################################################
 

+ 129 - 101
appTools/ToolFiducials.py

@@ -12,6 +12,7 @@ from appGUI.GUIElements import FCDoubleSpinner, RadioSet, EvalEntry, FCTable, FC
 
 from shapely.geometry import Point, Polygon, MultiPolygon, LineString
 from shapely.geometry import box as box
+from shapely.ops import unary_union
 
 import math
 import logging
@@ -76,6 +77,8 @@ class ToolFiducials(AppTool):
 
         self.click_points = []
 
+        self.handlers_connected = False
+
         # SIGNALS
         self.ui.add_cfid_button.clicked.connect(self.add_fiducials)
         self.ui.add_sm_opening_button.clicked.connect(self.add_soldermask_opening)
@@ -147,12 +150,13 @@ class ToolFiducials(AppTool):
     def on_method_change(self, val):
         """
         Make sure that on method change we disconnect the event handlers and reset the points storage
-        :param val: value of the Radio button which trigger this method
-        :return: None
+
+        :param val:     value of the Radio button which trigger this method
+        :return:        None
         """
-        if val == 'auto':
-            self.click_points = []
+        self.click_points = []
 
+        if val == 'auto':
             try:
                 self.disconnect_event_handlers()
             except TypeError:
@@ -228,10 +232,14 @@ class ToolFiducials(AppTool):
                 )
                 self.ui.sec_points_coords_entry.set_value('(%.*f, %.*f)' % (self.decimals, x1, self.decimals, y0))
 
-            self.add_fiducials_geo(self.click_points, g_obj=self.grb_object, fid_type=fid_type)
-            self.grb_object.source_file = self.app.f_handlers.export_gerber(obj_name=self.grb_object.options['name'],
-                                                                            filename=None,
-                                                                            local_use=self.grb_object, use_thread=False)
+            ret_val = self.add_fiducials_geo(self.click_points, g_obj=self.grb_object, fid_type=fid_type)
+            self.app.call_source = "app"
+            if ret_val == 'fail':
+                self.app.call_source = "app"
+                self.disconnect_event_handlers()
+                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed."))
+                return
+
             self.on_exit()
         else:
             self.app.inform.emit(_("Click to add first Fiducial. Bottom Left..."))
@@ -259,6 +267,8 @@ class ToolFiducials(AppTool):
 
         radius = fid_size / 2.0
 
+        new_apertures = deepcopy(g_obj.apertures)
+
         if fid_type == 'circular':
             geo_list = [Point(pt).buffer(radius, self.grb_steps_per_circle) for pt in points_list]
 
@@ -270,10 +280,8 @@ class ToolFiducials(AppTool):
 
             if aperture_found:
                 for geo in geo_list:
-                    dict_el = {}
-                    dict_el['follow'] = geo.centroid
-                    dict_el['solid'] = geo
-                    g_obj.apertures[aperture_found]['geometry'].append(deepcopy(dict_el))
+                    dict_el = {'follow': geo.centroid, 'solid': geo}
+                    new_apertures[aperture_found]['geometry'].append(deepcopy(dict_el))
             else:
                 ap_keys = list(g_obj.apertures.keys())
                 if ap_keys:
@@ -281,16 +289,15 @@ class ToolFiducials(AppTool):
                 else:
                     new_apid = '10'
 
-                g_obj.apertures[new_apid] = {}
-                g_obj.apertures[new_apid]['type'] = 'C'
-                g_obj.apertures[new_apid]['size'] = fid_size
-                g_obj.apertures[new_apid]['geometry'] = []
+                new_apertures[new_apid] = {
+                    'type': 'C',
+                    'size': fid_size,
+                    'geometry': []
+                }
 
                 for geo in geo_list:
-                    dict_el = {}
-                    dict_el['follow'] = geo.centroid
-                    dict_el['solid'] = geo
-                    g_obj.apertures[new_apid]['geometry'].append(deepcopy(dict_el))
+                    dict_el = {'follow': geo.centroid, 'solid': geo}
+                    new_apertures[new_apid]['geometry'].append(deepcopy(dict_el))
 
             s_list = []
             if g_obj.solid_geometry:
@@ -301,7 +308,6 @@ class ToolFiducials(AppTool):
                     s_list.append(g_obj.solid_geometry)
 
             s_list += geo_list
-            g_obj.solid_geometry = MultiPolygon(s_list)
         elif fid_type == 'cross':
             geo_list = []
 
@@ -330,13 +336,10 @@ class ToolFiducials(AppTool):
                     geo_buff_list.append(geo_buff_h)
                     geo_buff_list.append(geo_buff_v)
 
-                    dict_el = {}
-                    dict_el['follow'] = geo_buff_h.centroid
-                    dict_el['solid'] = geo_buff_h
-                    g_obj.apertures[aperture_found]['geometry'].append(deepcopy(dict_el))
-                    dict_el['follow'] = geo_buff_v.centroid
-                    dict_el['solid'] = geo_buff_v
-                    g_obj.apertures[aperture_found]['geometry'].append(deepcopy(dict_el))
+                    dict_el = {'follow': geo_buff_h.centroid, 'solid': geo_buff_h}
+                    new_apertures[aperture_found]['geometry'].append(deepcopy(dict_el))
+                    dict_el = {'follow': geo_buff_v.centroid, 'solid': geo_buff_v}
+                    new_apertures[aperture_found]['geometry'].append(deepcopy(dict_el))
             else:
                 ap_keys = list(g_obj.apertures.keys())
                 if ap_keys:
@@ -344,10 +347,11 @@ class ToolFiducials(AppTool):
                 else:
                     new_apid = '10'
 
-                g_obj.apertures[new_apid] = {}
-                g_obj.apertures[new_apid]['type'] = 'C'
-                g_obj.apertures[new_apid]['size'] = line_thickness
-                g_obj.apertures[new_apid]['geometry'] = []
+                new_apertures[new_apid] = {
+                    'type': 'C',
+                    'size': line_thickness,
+                    'geometry': []
+                }
 
                 for geo in geo_list:
                     geo_buff_h = geo[0].buffer(line_thickness / 2.0, self.grb_steps_per_circle)
@@ -355,13 +359,10 @@ class ToolFiducials(AppTool):
                     geo_buff_list.append(geo_buff_h)
                     geo_buff_list.append(geo_buff_v)
 
-                    dict_el = {}
-                    dict_el['follow'] = geo_buff_h.centroid
-                    dict_el['solid'] = geo_buff_h
-                    g_obj.apertures[new_apid]['geometry'].append(deepcopy(dict_el))
-                    dict_el['follow'] = geo_buff_v.centroid
-                    dict_el['solid'] = geo_buff_v
-                    g_obj.apertures[new_apid]['geometry'].append(deepcopy(dict_el))
+                    dict_el = {'follow': geo_buff_h.centroid, 'solid': geo_buff_h}
+                    new_apertures[new_apid]['geometry'].append(deepcopy(dict_el))
+                    dict_el = {'follow': geo_buff_v.centroid, 'solid': geo_buff_v}
+                    new_apertures[new_apid]['geometry'].append(deepcopy(dict_el))
 
             s_list = []
             if g_obj.solid_geometry:
@@ -375,7 +376,6 @@ class ToolFiducials(AppTool):
             geo_buff_list = geo_buff_list.buffer(0)
             for poly in geo_buff_list:
                 s_list.append(poly)
-            g_obj.solid_geometry = MultiPolygon(s_list)
         else:
             # chess pattern fiducial type
             geo_list = []
@@ -412,10 +412,8 @@ class ToolFiducials(AppTool):
                 for geo in geo_list:
                     geo_buff_list.append(geo)
 
-                    dict_el = {}
-                    dict_el['follow'] = geo.centroid
-                    dict_el['solid'] = geo
-                    g_obj.apertures[aperture_found]['geometry'].append(deepcopy(dict_el))
+                    dict_el = {'follow': geo.centroid, 'solid': geo}
+                    new_apertures[aperture_found]['geometry'].append(deepcopy(dict_el))
             else:
                 ap_keys = list(g_obj.apertures.keys())
                 if ap_keys:
@@ -423,20 +421,19 @@ class ToolFiducials(AppTool):
                 else:
                     new_apid = '10'
 
-                g_obj.apertures[new_apid] = {}
-                g_obj.apertures[new_apid]['type'] = 'R'
-                g_obj.apertures[new_apid]['size'] = new_ap_size
-                g_obj.apertures[new_apid]['width'] = fid_size
-                g_obj.apertures[new_apid]['height'] = fid_size
-                g_obj.apertures[new_apid]['geometry'] = []
+                new_apertures[new_apid] = {
+                    'type': 'R',
+                    'size': new_ap_size,
+                    'width': fid_size,
+                    'height': fid_size,
+                    'geometry': []
+                }
 
                 for geo in geo_list:
                     geo_buff_list.append(geo)
 
-                    dict_el = {}
-                    dict_el['follow'] = geo.centroid
-                    dict_el['solid'] = geo
-                    g_obj.apertures[new_apid]['geometry'].append(deepcopy(dict_el))
+                    dict_el = {'follow': geo.centroid, 'solid': geo}
+                    new_apertures[new_apid]['geometry'].append(deepcopy(dict_el))
 
             s_list = []
             if g_obj.solid_geometry:
@@ -448,7 +445,28 @@ class ToolFiducials(AppTool):
 
             for poly in geo_buff_list:
                 s_list.append(poly)
-            g_obj.solid_geometry = MultiPolygon(s_list)
+
+        outname = '%s_%s' % (str(g_obj.options['name']), 'fid')
+
+        def initialize(grb_obj, app_obj):
+            grb_obj.options = {}
+            for opt in g_obj.options:
+                if opt != 'name':
+                    grb_obj.options[opt] = deepcopy(g_obj.options[opt])
+            grb_obj.options['name'] = outname
+            grb_obj.multitool = False
+            grb_obj.multigeo = False
+            grb_obj.follow = deepcopy(g_obj.follow)
+            grb_obj.apertures = new_apertures
+            grb_obj.solid_geometry = unary_union(s_list)
+            grb_obj.follow_geometry = deepcopy(g_obj.follow_geometry) + geo_list
+
+            grb_obj.source_file = app_obj.f_handlers.export_gerber(obj_name=outname, filename=None, local_use=grb_obj,
+                                                                   use_thread=False)
+
+        ret = self.app.app_obj.new_object('gerber', outname, initialize, plot=True)
+
+        return ret
 
     def add_soldermask_opening(self):
         sm_opening_dia = self.ui.fid_size_entry.get_value() * 2.0
@@ -465,12 +483,15 @@ class ToolFiducials(AppTool):
             return
 
         self.sm_obj_set.add(self.sm_object.options['name'])
-        self.add_fiducials_geo(self.click_points, g_obj=self.sm_object, fid_size=sm_opening_dia, fid_type='circular')
+        ret_val = self.add_fiducials_geo(
+            self.click_points, g_obj=self.sm_object, fid_size=sm_opening_dia, fid_type='circular')
+        self.app.call_source = "app"
+        if ret_val == 'fail':
+            self.app.call_source = "app"
+            self.disconnect_event_handlers()
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed."))
+            return
 
-        self.sm_object.source_file = self.app.f_handlers.export_gerber(obj_name=self.sm_object.options['name'],
-                                                                       filename=None,
-                                                                       local_use=self.sm_object,
-                                                                       use_thread=False)
         self.on_exit()
 
     def on_mouse_release(self, event):
@@ -496,7 +517,7 @@ class ToolFiducials(AppTool):
             self.check_points()
 
     def check_points(self):
-        fid_type = self.fid_type_radio.get_value()
+        fid_type = self.ui.fid_type_radio.get_value()
 
         if len(self.click_points) == 1:
             self.ui.bottom_left_coords_entry.set_value(self.click_points[0])
@@ -508,20 +529,30 @@ class ToolFiducials(AppTool):
                 self.app.inform.emit(_("Click to add the second fiducial. Top Left or Bottom Right..."))
             elif len(self.click_points) == 3:
                 self.ui.sec_points_coords_entry.set_value(self.click_points[2])
-                self.app.inform.emit('[success] %s' % _("Done. All fiducials have been added."))
-                self.add_fiducials_geo(self.click_points, g_obj=self.grb_object, fid_type=fid_type)
-                self.grb_object.source_file = self.app.f_handlers.export_gerber(
-                    obj_name=self.grb_object.options['name'], filename=None, local_use=self.grb_object,
-                    use_thread=False)
+                self.app.inform.emit('[success] %s' % _("Done."))
+
+                ret_val = self.add_fiducials_geo(self.click_points, g_obj=self.grb_object, fid_type=fid_type)
+                self.app.call_source = "app"
+
+                if ret_val == 'fail':
+                    self.app.call_source = "app"
+                    self.disconnect_event_handlers()
+                    self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed."))
+                    return
                 self.on_exit()
         else:
             if len(self.click_points) == 2:
                 self.ui.top_right_coords_entry.set_value(self.click_points[1])
-                self.app.inform.emit('[success] %s' % _("Done. All fiducials have been added."))
-                self.add_fiducials_geo(self.click_points, g_obj=self.grb_object, fid_type=fid_type)
-                self.grb_object.source_file = self.app.f_handlers.export_gerber(
-                    obj_name=self.grb_object.options['name'], filename=None,
-                    local_use=self.grb_object, use_thread=False)
+                self.app.inform.emit('[success] %s' % _("Done."))
+
+                ret_val = self.add_fiducials_geo(self.click_points, g_obj=self.grb_object, fid_type=fid_type)
+                self.app.call_source = "app"
+
+                if ret_val == 'fail':
+                    self.app.call_source = "app"
+                    self.disconnect_event_handlers()
+                    self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed."))
+                    return
                 self.on_exit()
 
     def on_mouse_move(self, event):
@@ -529,8 +560,9 @@ class ToolFiducials(AppTool):
 
     def replot(self, obj, run_thread=True):
         def worker_task():
-            with self.app.proc_container.new('%s...' % _("Plotting")):
+            with self.app.proc_container.new('%s ...' % _("Plotting")):
                 obj.plot()
+                self.app.app_obj.object_plotted.emit(obj)
 
         if run_thread:
             self.app.worker_task.emit({'fcn': worker_task, 'params': []})
@@ -579,10 +611,6 @@ class ToolFiducials(AppTool):
             except Exception as e:
                 log.debug("ToolFiducials.on_exit() sm_obj bounds error --> %s" % str(e))
 
-        # reset the variables
-        self.grb_object = None
-        self.sm_object = None
-
         # Events ID
         self.mr = None
         # self.mm = None
@@ -597,32 +625,31 @@ class ToolFiducials(AppTool):
         self.app.inform.emit('[success] %s' % _("Fiducials Tool exit."))
 
     def connect_event_handlers(self):
-        if self.app.is_legacy is False:
-            self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
-            # self.app.plotcanvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
-            self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
-        else:
-            self.app.plotcanvas.graph_event_disconnect(self.app.mp)
-            # self.app.plotcanvas.graph_event_disconnect(self.app.mm)
-            self.app.plotcanvas.graph_event_disconnect(self.app.mr)
+        if self.handlers_connected is False:
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
+                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.app.mp)
+                self.app.plotcanvas.graph_event_disconnect(self.app.mr)
 
-        self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_release)
-        # self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move)
+            self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_release)
+
+            self.handlers_connected = True
 
     def disconnect_event_handlers(self):
-        if self.app.is_legacy is False:
-            self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
-            # self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
-        else:
-            self.app.plotcanvas.graph_event_disconnect(self.mr)
-            # self.app.plotcanvas.graph_event_disconnect(self.mm)
+        if self.handlers_connected is True:
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.mr)
+
+            self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
+                                                                  self.app.on_mouse_click_over_plot)
 
-        self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
-                                                              self.app.on_mouse_click_over_plot)
-        # self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move',
-        #                                                       self.app.on_mouse_move_over_plot)
-        self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
-                                                              self.app.on_mouse_click_release_over_plot)
+            self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
+                                                                  self.app.on_mouse_click_release_over_plot)
+            self.handlers_connected = False
 
     def flatten(self, geometry):
         """
@@ -790,7 +817,7 @@ class FidoUI:
             _("Bounding box margin.")
         )
         self.margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.margin_entry.set_range(-9999.9999, 9999.9999)
+        self.margin_entry.set_range(-10000.0000, 10000.0000)
         self.margin_entry.set_precision(self.decimals)
         self.margin_entry.setSingleStep(0.1)
 
@@ -804,7 +831,7 @@ class FidoUI:
         ], stretch=False)
         self.mode_label = QtWidgets.QLabel(_("Mode:"))
         self.mode_label.setToolTip(
-            _("- 'Auto' - automatic placement of fiducials in the corners of the bounding box.\n "
+            _("- 'Auto' - automatic placement of fiducials in the corners of the bounding box.\n"
               "- 'Manual' - manual placement of fiducials.")
         )
         grid_lay.addWidget(self.mode_label, 3, 0)
@@ -853,7 +880,7 @@ class FidoUI:
             _("Thickness of the line that makes the fiducial.")
         )
         self.line_thickness_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.line_thickness_entry.set_range(0.00001, 9999.9999)
+        self.line_thickness_entry.set_range(0.00001, 10000.0000)
         self.line_thickness_entry.set_precision(self.decimals)
         self.line_thickness_entry.setSingleStep(0.1)
 
@@ -882,6 +909,7 @@ class FidoUI:
 
         # ## Insert Copper Fiducial
         self.add_cfid_button = QtWidgets.QPushButton(_("Add Fiducial"))
+        self.add_cfid_button.setIcon(QtGui.QIcon(self.app.resource_location + '/fiducials_32.png'))
         self.add_cfid_button.setToolTip(
             _("Will add a polygon on the copper layer to serve as fiducial.")
         )

+ 20 - 15
appTools/ToolFilm.py

@@ -156,19 +156,19 @@ class Film(AppTool):
         try:
             name = self.ui.tf_object_combo.currentText()
         except Exception:
-            self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                 _("No FlatCAM object selected. Load an object for Film and retry."))
+            self.app.inform.emit('[ERROR_NOTCL] %s %s' %
+                                 (_("No object is selected."), _("Load an object for Film and retry.")))
             return
 
         try:
             boxname = self.ui.tf_box_combo.currentText()
         except Exception:
-            self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                 _("No FlatCAM object selected. Load an object for Box and retry."))
+            self.app.inform.emit('[ERROR_NOTCL] %s %s' %
+                                 (_("No object is selected."), _("Load an object for Box and retry.")))
             return
 
         if name == '' or boxname == '':
-            self.app.inform.emit('[ERROR_NOTCL] %s' % _("No FlatCAM object selected."))
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _("No object is selected."))
             return
 
         scale_stroke_width = float(self.ui.film_scale_stroke_entry.get_value())
@@ -232,7 +232,9 @@ class Film(AppTool):
                 directory=self.app.get_last_save_folder() + '/' + name + '_film',
                 ext_filter=filter_ext)
         except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export positive film"))
+            filename, _f = FCFileSaveDialog.get_saved_filename(
+                caption=_("Export positive film"),
+                ext_filter=filter_ext)
 
         filename = str(filename)
 
@@ -289,7 +291,7 @@ class Film(AppTool):
                 if film_obj.apertures[apid]['type'] == 'C':
                     if punch_size >= float(film_obj.apertures[apid]['size']):
                         self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                             _(" Could not generate punched hole film because the punch hole size"
+                                             _("Failed. Punch hole size "
                                                "is bigger than some of the apertures in the Gerber object."))
                         return 'fail'
                     else:
@@ -301,7 +303,7 @@ class Film(AppTool):
                     if punch_size >= float(film_obj.apertures[apid]['width']) or \
                             punch_size >= float(film_obj.apertures[apid]['height']):
                         self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                             _("Could not generate punched hole film because the punch hole size"
+                                             _("Failed. Punch hole size "
                                                "is bigger than some of the apertures in the Gerber object."))
                         return 'fail'
                     else:
@@ -319,7 +321,7 @@ class Film(AppTool):
 
             if punched_solid_geometry == temp_solid_geometry:
                 self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                     _("Could not generate punched hole film because the newly created object geometry "
+                                     _("Failed. The new object geometry "
                                        "is the same as the one in the source object geometry..."))
                 return 'fail'
 
@@ -378,7 +380,9 @@ class Film(AppTool):
                 directory=self.app.get_last_save_folder() + '/' + name + '_film',
                 ext_filter=filter_ext)
         except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export negative film"))
+            filename, _f = FCFileSaveDialog.get_saved_filename(
+                caption=_("Export negative film"),
+                ext_filter=filter_ext)
 
         filename = str(filename)
 
@@ -609,7 +613,8 @@ class Film(AppTool):
                     drawing = svg2rlg(doc_final)
 
                     p_size = self.ui.pagesize_combo.get_value()
-                    if p_size == 'Bounds':                        renderPDF.drawToFile(drawing, filename)
+                    if p_size == 'Bounds':
+                        renderPDF.drawToFile(drawing, filename)
                     else:
                         if self.ui.orientation_radio.get_value() == 'p':
                             page_size = portrait(self.ui.pagesize[p_size])
@@ -950,7 +955,7 @@ class FilmUI:
         self.tf_type_box_combo = RadioSet([{'label': _('Gerber'), 'value': 'grb'},
                                            {'label': _('Geometry'), 'value': 'geo'}])
 
-        self.tf_type_box_combo_label = FCLabel(_("Box Type:"))
+        self.tf_type_box_combo_label = FCLabel('%s:' % _("Box Type"))
         self.tf_type_box_combo_label.setToolTip(
             _("Specify the type of object to be used as an container for\n"
               "film creation. It can be: Gerber or Geometry type."
@@ -1103,7 +1108,7 @@ class FilmUI:
                                           {'label': _('Y'), 'value': 'y'},
                                           {'label': _('Both'), 'value': 'both'}],
                                          stretch=False)
-        self.film_mirror_axis_label = FCLabel('%s:' % _("Mirror axis"))
+        self.film_mirror_axis_label = FCLabel('%s:' % _("Mirror Axis"))
 
         grid0.addWidget(self.film_mirror_axis_label, 16, 0)
         grid0.addWidget(self.film_mirror_axis, 16, 1)
@@ -1142,7 +1147,7 @@ class FilmUI:
         self.film_type = RadioSet([{'label': _('Positive'), 'value': 'pos'},
                                    {'label': _('Negative'), 'value': 'neg'}],
                                   stretch=False)
-        self.film_type_label = FCLabel(_("Film Type:"))
+        self.film_type_label = FCLabel('%s:' % _("Film Type"))
         self.film_type_label.setToolTip(
             _("Generate a Positive black film or a Negative film.\n"
               "Positive means that it will print the features\n"
@@ -1254,7 +1259,7 @@ class FilmUI:
                                          {'label': _('PDF'), 'value': 'pdf'}
                                          ], stretch=False)
 
-        self.file_type_label = FCLabel(_("Film Type:"))
+        self.file_type_label = FCLabel('%s:' % _("Film Type"))
         self.file_type_label.setToolTip(
             _("The file type of the saved film. Can be:\n"
               "- 'SVG' -> open-source vectorial format\n"

+ 3 - 4
appTools/ToolImage.py

@@ -79,11 +79,10 @@ class ToolImage(AppTool):
     def on_file_importimage(self):
         """
         Callback for menu item File->Import IMAGE.
-        :param type_of_obj: to import the IMAGE as Geometry or as Gerber
-        :type type_of_obj: str
+
         :return: None
         """
-        mask = []
+
         self.app.log.debug("on_file_importimage()")
 
         _filter = "Image Files(*.BMP *.PNG *.JPG *.JPEG);;" \
@@ -147,7 +146,7 @@ class ToolImage(AppTool):
             geo_obj.import_image(filename, units=units, dpi=dpi, mode=mode, mask=mask)
             geo_obj.multigeo = False
 
-        with self.app.proc_container.new(_("Importing Image")) as proc:
+        with self.app.proc_container.new('%s ...' % _("Importing")):
 
             # Object name
             name = outname or filename.split('/')[-1].split('\\')[-1]

+ 13 - 15
appTools/ToolInvertGerber.py

@@ -44,7 +44,7 @@ class ToolInvertGerber(AppTool):
         self.ui.reset_button.clicked.connect(self.set_tool_ui)
 
     def install(self, icon=None, separator=None, **kwargs):
-        AppTool.install(self, icon, separator, shortcut='', **kwargs)
+        AppTool.install(self, icon, separator, shortcut='ALT+G', **kwargs)
 
     def run(self, toggle=True):
         self.app.defaults.report_usage("ToolInvertGerber()")
@@ -131,21 +131,18 @@ class ToolInvertGerber(AppTool):
         new_apertures = {}
 
         if '0' not in new_apertures:
-            new_apertures['0'] = {}
-            new_apertures['0']['type'] = 'C'
-            new_apertures['0']['size'] = 0.0
-            new_apertures['0']['geometry'] = []
+            new_apertures['0'] = {
+                'type': 'C',
+                'size': 0.0,
+                'geometry': []
+            }
 
         try:
             for poly in new_solid_geometry:
-                new_el = {}
-                new_el['solid'] = poly
-                new_el['follow'] = poly.exterior
+                new_el = {'solid': poly, 'follow': poly.exterior}
                 new_apertures['0']['geometry'].append(new_el)
         except TypeError:
-            new_el = {}
-            new_el['solid'] = new_solid_geometry
-            new_el['follow'] = new_solid_geometry.exterior
+            new_el = {'solid': new_solid_geometry, 'follow': new_solid_geometry.exterior}
             new_apertures['0']['geometry'].append(new_el)
 
         def init_func(new_obj, app_obj):
@@ -157,8 +154,8 @@ class ToolInvertGerber(AppTool):
             new_obj.apertures = deepcopy(new_apertures)
 
             new_obj.solid_geometry = deepcopy(new_solid_geometry)
-            new_obj.source_file = self.app.f_handlers.export_gerber(obj_name=outname, filename=None,
-                                                                    local_use=new_obj, use_thread=False)
+            new_obj.source_file = app_obj.f_handlers.export_gerber(obj_name=outname, filename=None,
+                                                                   local_use=new_obj, use_thread=False)
 
         self.app.app_obj.new_object('gerber', outname, init_func)
 
@@ -238,7 +235,7 @@ class InvertUI:
         )
         self.margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.margin_entry.set_precision(self.decimals)
-        self.margin_entry.set_range(0.0000, 9999.9999)
+        self.margin_entry.set_range(0.0000, 10000.0000)
         self.margin_entry.setObjectName(_("Margin"))
 
         grid0.addWidget(self.margin_label, 5, 0, 1, 2)
@@ -267,6 +264,7 @@ class InvertUI:
         grid0.addWidget(separator_line, 9, 0, 1, 2)
 
         self.invert_btn = FCButton(_('Invert Gerber'))
+        self.invert_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/invert32.png'))
         self.invert_btn.setToolTip(
             _("Will invert the Gerber object: areas that have copper\n"
               "will be empty of copper and previous empty area will be\n"
@@ -314,4 +312,4 @@ class InvertUI:
             self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
                                             (_("Edited value is out of range"), minval, maxval), False)
         else:
-            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
+            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)

+ 213 - 120
appTools/ToolIsolation.py

@@ -8,8 +8,8 @@
 from PyQt5 import QtWidgets, QtCore, QtGui
 
 from appTool import AppTool
-from appGUI.GUIElements import FCCheckBox, FCDoubleSpinner, RadioSet, FCTable, FCInputDialog, FCButton, \
-    FCComboBox, OptionalInputSection, FCSpinner, FCLabel
+from appGUI.GUIElements import FCCheckBox, FCDoubleSpinner, RadioSet, FCTable, FCButton, \
+    FCComboBox, OptionalInputSection, FCSpinner, FCLabel, FCInputDialogSpinnerButton, FCComboBox2
 from appParsers.ParseGerber import Gerber
 from camlib import grace
 
@@ -58,7 +58,8 @@ class ToolIsolation(AppTool, Gerber):
         # #############################################################################
         self.ui.tools_table.setupContextMenu()
         self.ui.tools_table.addContextMenu(
-            _("Search and Add"), self.on_add_tool_by_key,
+            _("Search and Add"),
+            self.on_add_tool_by_key,
             icon=QtGui.QIcon(self.app.resource_location + "/plus16.png")
         )
         self.ui.tools_table.addContextMenu(
@@ -207,7 +208,7 @@ class ToolIsolation(AppTool, Gerber):
         self.ui.apply_param_to_all.clicked.connect(self.on_apply_param_to_all_clicked)
 
         # adding Tools
-        self.ui.add_newtool_button.clicked.connect(lambda: self.on_tool_add())
+        self.ui.search_and_add_btn.clicked.connect(lambda: self.on_tool_add())
         self.ui.addtool_from_db_btn.clicked.connect(self.on_tool_add_from_db_clicked)
 
         self.ui.generate_iso_button.clicked.connect(self.on_iso_button_click)
@@ -779,35 +780,47 @@ class ToolIsolation(AppTool, Gerber):
         self.blockSignals(False)
 
     def on_add_tool_by_key(self):
-        tool_add_popup = FCInputDialog(title='%s...' % _("New Tool"),
-                                       text='%s:' % _('Enter a Tool Diameter'),
-                                       min=0.0001, max=9999.9999, decimals=self.decimals)
+        # tool_add_popup = FCInputDialog(title='%s...' % _("New Tool"),
+        #                                text='%s:' % _('Enter a Tool Diameter'),
+        #                                min=0.0001, max=10000.0000, decimals=self.decimals)
+        btn_icon = QtGui.QIcon(self.app.resource_location + '/open_excellon32.png')
+
+        tool_add_popup = FCInputDialogSpinnerButton(title='%s...' % _("New Tool"),
+                                                    text='%s:' % _('Enter a Tool Diameter'),
+                                                    min=0.0001, max=10000.0000, decimals=self.decimals,
+                                                    button_icon=btn_icon,
+                                                    callback=self.on_find_optimal_tooldia,
+                                                    parent=self.app.ui)
         tool_add_popup.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/letter_t_32.png'))
 
-        val, ok = tool_add_popup.get_value()
+        def find_optimal(valor):
+            tool_add_popup.set_value(float(valor))
+
+        self.optimal_found_sig.connect(find_optimal)
+
+        val, ok = tool_add_popup.get_results()
         if ok:
             if float(val) == 0:
                 self.app.inform.emit('[WARNING_NOTCL] %s' %
                                      _("Please enter a tool diameter with non-zero value, in Float format."))
+                self.optimal_found_sig.disconnect(find_optimal)
                 return
             self.on_tool_add(custom_dia=float(val))
         else:
             self.app.inform.emit('[WARNING_NOTCL] %s...' % _("Adding Tool cancelled"))
+        self.optimal_found_sig.disconnect(find_optimal)
 
     def on_reference_combo_changed(self):
         obj_type = self.ui.reference_combo_type.currentIndex()
         self.ui.reference_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
         self.ui.reference_combo.setCurrentIndex(0)
-        self.ui.reference_combo.obj_type = {
-            _("Gerber"): "Gerber", _("Excellon"): "Excellon", _("Geometry"): "Geometry"
-        }[self.ui.reference_combo_type.get_value()]
+        self.ui.reference_combo.obj_type = {0: "Gerber", 1: "Excellon", 2: "Geometry"}[obj_type]
 
     def on_toggle_reference(self):
         val = self.ui.select_combo.get_value()
 
-        if val == _("All"):
+        if val == 0:    # ALl
             self.ui.reference_combo.hide()
-            self.ui.reference_combo_label.hide()
             self.ui.reference_combo_type.hide()
             self.ui.reference_combo_type_label.hide()
             self.ui.area_shape_label.hide()
@@ -816,9 +829,8 @@ class ToolIsolation(AppTool, Gerber):
 
             # disable rest-machining for area painting
             self.ui.rest_cb.setDisabled(False)
-        elif val == _("Area Selection"):
+        elif val == 1:  # Area Selection
             self.ui.reference_combo.hide()
-            self.ui.reference_combo_label.hide()
             self.ui.reference_combo_type.hide()
             self.ui.reference_combo_type_label.hide()
             self.ui.area_shape_label.show()
@@ -828,17 +840,15 @@ class ToolIsolation(AppTool, Gerber):
             # disable rest-machining for area isolation
             self.ui.rest_cb.set_value(False)
             self.ui.rest_cb.setDisabled(True)
-        elif val == _("Polygon Selection"):
+        elif val == 2:  # Polygon Selection
             self.ui.reference_combo.hide()
-            self.ui.reference_combo_label.hide()
             self.ui.reference_combo_type.hide()
             self.ui.reference_combo_type_label.hide()
             self.ui.area_shape_label.hide()
             self.ui.area_shape_radio.hide()
             self.ui.poly_int_cb.show()
-        else:
+        else:   # Reference Object
             self.ui.reference_combo.show()
-            self.ui.reference_combo_label.show()
             self.ui.reference_combo_type.show()
             self.ui.reference_combo_type_label.show()
             self.ui.area_shape_label.hide()
@@ -886,7 +896,7 @@ class ToolIsolation(AppTool, Gerber):
         # if the sender is in the column with index 2 then we update the tool_type key
         if cw_col == 2:
             tt = cw.currentText()
-            typ = 'Iso' if tt == 'V' else "Rough"
+            typ = 'Iso' if tt == 'V' else 'Rough'
 
             self.iso_tools[currenuid].update({
                 'type': typ,
@@ -894,9 +904,124 @@ class ToolIsolation(AppTool, Gerber):
             })
 
     def on_find_optimal_tooldia(self):
-        self.find_safe_tooldia_worker(is_displayed=True)
+        self.find_safe_tooldia_worker()
+
+    @staticmethod
+    def find_optim_mp(aperture_storage, decimals):
+        msg = 'ok'
+        total_geo = []
+
+        for ap in list(aperture_storage.keys()):
+            if 'geometry' in aperture_storage[ap]:
+                for geo_el in aperture_storage[ap]['geometry']:
+                    if 'solid' in geo_el and geo_el['solid'] is not None and geo_el['solid'].is_valid:
+                        total_geo.append(geo_el['solid'])
+
+        total_geo = MultiPolygon(total_geo)
+        total_geo = total_geo.buffer(0)
+
+        try:
+            __ = iter(total_geo)
+            geo_len = len(total_geo)
+        except TypeError:
+            msg = ('[ERROR_NOTCL] %s' % _("The Gerber object has one Polygon as geometry.\n"
+                                          "There are no distances between geometry elements to be found."))
+
+        min_dict = {}
+        idx = 1
+        for geo in total_geo:
+            for s_geo in total_geo[idx:]:
+                # minimize the number of distances by not taking into considerations
+                # those that are too small
+                dist = geo.distance(s_geo)
+                dist = float('%.*f' % (decimals, dist))
+                loc_1, loc_2 = nearest_points(geo, s_geo)
+
+                proc_loc = (
+                    (float('%.*f' % (decimals, loc_1.x)), float('%.*f' % (decimals, loc_1.y))),
+                    (float('%.*f' % (decimals, loc_2.x)), float('%.*f' % (decimals, loc_2.y)))
+                )
+
+                if dist in min_dict:
+                    min_dict[dist].append(proc_loc)
+                else:
+                    min_dict[dist] = [proc_loc]
+
+            idx += 1
+
+        min_list = list(min_dict.keys())
+        min_dist = min(min_list)
+
+        return msg, min_dist
+
+    # multiprocessing variant
+    def find_safe_tooldia_multiprocessing(self):
+        self.app.inform.emit(_("Checking tools for validity."))
+        self.units = self.app.defaults['units'].upper()
+
+        obj_name = self.ui.object_combo.currentText()
+
+        # Get source object.
+        try:
+            fcobj = self.app.collection.get_by_name(obj_name)
+        except Exception:
+            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(obj_name)))
+            return
+
+        if fcobj is None:
+            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(obj_name)))
+            return
+
+        def job_thread(app_obj):
+            with self.app.proc_container.new(_("Checking ...")):
+
+                ap_storage = fcobj.apertures
 
-    def find_safe_tooldia_worker(self, is_displayed):
+                p = app_obj.pool.apply_async(self.find_optim_mp, args=(ap_storage, self.decimals))
+                res = p.get()
+
+                if res[0] != 'ok':
+                    app_obj.inform.emit(res[0])
+                    return 'fail'
+                else:
+                    min_dist = res[1]
+
+                try:
+                    min_dist_truncated = self.app.dec_format(float(min_dist), self.decimals)
+                    self.safe_tooldia = min_dist_truncated
+
+                    if self.safe_tooldia:
+                        # find the selected tool ID's
+                        sorted_tools = []
+                        table_items = self.ui.tools_table.selectedItems()
+                        sel_rows = {t.row() for t in table_items}
+                        for row in sel_rows:
+                            tid = int(self.ui.tools_table.item(row, 3).text())
+                            sorted_tools.append(tid)
+                        if not sorted_tools:
+                            msg = _("There are no tools selected in the Tool Table.")
+                            self.app.inform.emit('[ERROR_NOTCL] %s' % msg)
+                            return 'fail'
+
+                        # check if the tools diameters are less then the safe tool diameter
+                        for tool in sorted_tools:
+                            tool_dia = float(self.iso_tools[tool]['tooldia'])
+                            if tool_dia > self.safe_tooldia:
+                                msg = _("Incomplete isolation. "
+                                        "At least one tool could not do a complete isolation.")
+                                self.app.inform.emit('[WARNING] %s' % msg)
+                                break
+
+                        # reset the value to prepare for another isolation
+                        self.safe_tooldia = None
+                except Exception as ee:
+                    log.debug(str(ee))
+                    return
+
+        self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
+
+    def find_safe_tooldia_worker(self):
+        self.app.inform.emit(_("Checking tools for validity."))
         self.units = self.app.defaults['units'].upper()
 
         obj_name = self.ui.object_combo.currentText()
@@ -912,8 +1037,8 @@ class ToolIsolation(AppTool, Gerber):
             self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(obj_name)))
             return
 
-        def job_thread(app_obj, is_display):
-            with self.app.proc_container.new(_("Working...")) as proc:
+        def job_thread(app_obj):
+            with self.app.proc_container.new(_("Checking ...")):
                 try:
                     old_disp_number = 0
                     pol_nr = 0
@@ -981,69 +1106,40 @@ class ToolIsolation(AppTool, Gerber):
                     min_dist_truncated = self.app.dec_format(float(min_dist), self.decimals)
                     self.safe_tooldia = min_dist_truncated
 
-                    if is_display:
-                        self.optimal_found_sig.emit(min_dist_truncated)
+                    self.optimal_found_sig.emit(min_dist_truncated)
 
-                        app_obj.inform.emit('[success] %s: %s %s' %
-                                            (_("Optimal tool diameter found"), str(min_dist_truncated),
-                                             self.units.lower()))
-                    else:
-                        if self.safe_tooldia:
-                            # find the selected tool ID's
-                            sorted_tools = []
-                            table_items = self.ui.tools_table.selectedItems()
-                            sel_rows = {t.row() for t in table_items}
-                            for row in sel_rows:
-                                tid = int(self.ui.tools_table.item(row, 3).text())
-                                sorted_tools.append(tid)
-                            if not sorted_tools:
-                                msg = _("There are no tools selected in the Tool Table.")
-                                self.app.inform.emit('[ERROR_NOTCL] %s' % msg)
-                                return 'fail'
-
-                            # check if the tools diameters are less then the safe tool diameter
-                            for tool in sorted_tools:
-                                tool_dia = float(self.iso_tools[tool]['tooldia'])
-                                if tool_dia > self.safe_tooldia:
-                                    msg = _("Incomplete isolation. "
-                                            "At least one tool could not do a complete isolation.")
-                                    self.app.inform.emit('[WARNING] %s' % msg)
-                                    break
-
-                            # reset the value to prepare for another isolation
-                            self.safe_tooldia = None
+                    app_obj.inform.emit('[success] %s: %s %s' %
+                                        (_("Optimal tool diameter found"), str(min_dist_truncated),
+                                         self.units.lower()))
                 except Exception as ee:
                     log.debug(str(ee))
                     return
 
-        self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app, is_displayed]})
+        self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
 
     def on_tool_add(self, custom_dia=None):
         self.blockSignals(True)
 
-        filename = self.app.data_path + '\\tools_db.FlatDB'
-
-        new_tools_dict = deepcopy(self.default_data)
-        updated_tooldia = None
+        filename = self.app.tools_database_path()
 
+        tool_dia = custom_dia if custom_dia is not None else self.ui.new_tooldia_entry.get_value()
         # construct a list of all 'tooluid' in the self.iso_tools
         tool_uid_list = [int(tooluid_key) for tooluid_key in self.iso_tools]
 
         # find maximum from the temp_uid, add 1 and this is the new 'tooluid'
         max_uid = 0 if not tool_uid_list else max(tool_uid_list)
-        tooluid = int(max_uid + 1)
+        tooluid = int(max_uid) + 1
+
+        new_tools_dict = deepcopy(self.default_data)
+        updated_tooldia = None
 
         tool_dias = []
         for k, v in self.iso_tools.items():
             for tool_v in v.keys():
                 if tool_v == 'tooldia':
-                    tool_dias.append(self.app.dec_format(v[tool_v], self.decimals))
+                    tool_dias.append(self.app.dec_format(v['tooldia'], self.decimals))
 
         # determine the new tool diameter
-        if custom_dia is None:
-            tool_dia = self.ui.new_tooldia_entry.get_value()
-        else:
-            tool_dia = custom_dia
         if tool_dia is None or tool_dia == 0:
             self.build_ui()
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("Please enter a tool diameter with non-zero value, "
@@ -1084,7 +1180,7 @@ class ToolIsolation(AppTool, Gerber):
 
         offset = 'Path'
         offset_val = 0.0
-        typ = "Rough"
+        typ = 'Rough'
         tool_type = 'V'
         # look in database tools
         for db_tool, db_tool_val in tools_db_dict.items():
@@ -1098,7 +1194,7 @@ class ToolIsolation(AppTool, Gerber):
             high_limit = float(db_tool_val['data']['tol_max'])
 
             # we need only tool marked for Isolation Tool
-            if db_tool_val['data']['tool_target'] != _('Isolation'):
+            if db_tool_val['data']['tool_target'] != 3:     # _('Isolation')
                 continue
 
             # if we find a tool with the same diameter in the Tools DB just update it's data
@@ -1173,12 +1269,8 @@ class ToolIsolation(AppTool, Gerber):
 
     def on_tool_default_add(self, dia=None, muted=None):
         self.blockSignals(True)
-        self.units = self.app.defaults['units'].upper()
 
-        if dia:
-            tool_dia = dia
-        else:
-            tool_dia = self.ui.new_tooldia_entry.get_value()
+        tool_dia = dia if dia is not None else self.ui.new_tooldia_entry.get_value()
 
         if tool_dia is None or tool_dia == 0:
             self.build_ui()
@@ -1356,7 +1448,7 @@ class ToolIsolation(AppTool, Gerber):
 
                 self.grb_obj.solid_geometry = self.grb_obj.solid_geometry.buffer(0.0000001)
                 self.grb_obj.solid_geometry = self.grb_obj.solid_geometry.buffer(-0.0000001)
-                app_obj.inform.emit('[success] %s.' % _("Done"))
+                app_obj.inform.emit('[success] %s' % _("Done."))
                 self.grb_obj.plot_single_object.emit()
 
         self.app.worker_task.emit({'fcn': buffer_task, 'params': [self.app]})
@@ -1377,10 +1469,10 @@ class ToolIsolation(AppTool, Gerber):
             return
 
         if self.ui.valid_cb.get_value() is True:
-            self.find_safe_tooldia_worker(is_displayed=False)
+            self.find_safe_tooldia_multiprocessing()
 
         def worker_task(iso_obj):
-            with self.app.proc_container.new(_("Isolating...")):
+            with self.app.proc_container.new('%s ...' % _("Isolating")):
                 self.isolate_handler(iso_obj)
 
         self.app.worker_task.emit({'fcn': worker_task, 'params': [self.grb_obj]})
@@ -1430,9 +1522,9 @@ class ToolIsolation(AppTool, Gerber):
         """
         selection = self.ui.select_combo.get_value()
 
-        if selection == _("All"):
+        if selection == 0:  # ALL
             self.isolate(isolated_obj=isolated_obj)
-        elif selection == _("Area Selection"):
+        elif selection == 1:    # Area Selection
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the start point of the area."))
 
             if self.app.is_legacy is False:
@@ -1451,7 +1543,7 @@ class ToolIsolation(AppTool, Gerber):
             # disconnect flags
             self.area_sel_disconnect_flag = True
 
-        elif selection == _("Polygon Selection"):
+        elif selection == 2:    # Polygon Selection
             # disengage the grid snapping since it may be hard to click on polygons with grid snapping on
             if self.app.ui.grid_snap_btn.isChecked():
                 self.grid_status_memory = True
@@ -1472,7 +1564,7 @@ class ToolIsolation(AppTool, Gerber):
             # disconnect flags
             self.poly_sel_disconnect_flag = True
 
-        elif selection == _("Reference Object"):
+        elif selection == 3:    # Reference Object
             ref_obj = self.app.collection.get_by_name(self.ui.reference_combo.get_value())
             ref_geo = unary_union(ref_obj.solid_geometry)
             use_geo = unary_union(isolated_obj.solid_geometry).difference(ref_geo)
@@ -1613,14 +1705,13 @@ class ToolIsolation(AppTool, Gerber):
                             self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
                             geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry)
 
-                        geo_obj.tools = {}
-                        geo_obj.tools['1'] = {}
+                        geo_obj.tools = {'1': {}}
                         geo_obj.tools.update({
                             '1': {
                                 'tooldia':          float(tool_dia),
                                 'offset':           'Path',
                                 'offset_value':     0.0,
-                                'type':             _('Rough'),
+                                'type':             'Rough',
                                 'tool_type':        tool_type,
                                 'data':             tool_data,
                                 'solid_geometry':   geo_obj.solid_geometry
@@ -1782,7 +1873,7 @@ class ToolIsolation(AppTool, Gerber):
                             'tooldia':          float(tool_dia),
                             'offset':           'Path',
                             'offset_value':     0.0,
-                            'type':             _('Rough'),
+                            'type':             'Rough',
                             'tool_type':        tool_type,
                             'data':             tool_data,
                             'solid_geometry':   deepcopy(new_solid_geo)
@@ -1899,7 +1990,10 @@ class ToolIsolation(AppTool, Gerber):
 
         for tool in sorted_tools:
             tool_dia = tools_storage[tool]['tooldia']
+            tool_has_offset = tools_storage[tool]['offset']
+            tool_offset_value = tools_storage[tool]['offset_value']
             tool_type = tools_storage[tool]['tool_type']
+            tool_cut_type = tools_storage[tool]['type']
             tool_data = tools_storage[tool]['data']
 
             to_follow = tool_data['tools_iso_follow']
@@ -1973,9 +2067,9 @@ class ToolIsolation(AppTool, Gerber):
             tools_storage.update({
                 tool: {
                     'tooldia':          float(tool_dia),
-                    'offset':           'Path',
-                    'offset_value':     0.0,
-                    'type':             _('Rough'),
+                    'offset':           tool_has_offset,
+                    'offset_value':     tool_offset_value,
+                    'type':             tool_cut_type,
                     'tool_type':        tool_type,
                     'data':             tool_data,
                     'solid_geometry':   deepcopy(new_solid_geo)
@@ -2194,7 +2288,7 @@ class ToolIsolation(AppTool, Gerber):
                     self.poly_dict[shape_id] = clicked_poly
                     self.app.inform.emit(
                         '%s: %d. %s' % (_("Added polygon"), int(len(self.poly_dict)),
-                                        _("Click to add next polygon or right click to start isolation."))
+                                        _("Click to add next polygon or right click to start."))
                     )
                 else:
                     try:
@@ -2207,7 +2301,7 @@ class ToolIsolation(AppTool, Gerber):
                         return
                     self.app.inform.emit(
                         '%s. %s' % (_("Removed polygon"),
-                                    _("Click to add/remove next polygon or right click to start isolation."))
+                                    _("Click to add/remove next polygon or right click to start."))
                     )
 
                 self.app.tool_shapes.redraw()
@@ -2308,13 +2402,15 @@ class ToolIsolation(AppTool, Gerber):
             self.app.inform.emit(
                 '%s: %d. %s' % (_("Added polygon"),
                                 int(added_poly_count),
-                                _("Click to add next polygon or right click to start isolation."))
+                                _("Click to add next polygon or right click to start."))
             )
         else:
             self.app.inform.emit(_("No polygon in selection."))
 
     # To be called after clicking on the plot.
     def on_mouse_release(self, event):
+        shape_type = self.ui.area_shape_radio.get_value()
+
         if self.app.is_legacy is False:
             event_pos = event.pos
             # event_is_dragging = event.is_dragging
@@ -2332,8 +2428,6 @@ class ToolIsolation(AppTool, Gerber):
 
         x1, y1 = curr_pos[0], curr_pos[1]
 
-        shape_type = self.area_shape_radio.get_value()
-
         # do clear area only for left mouse clicks
         if event.button == 1:
             if shape_type == "square":
@@ -2573,7 +2667,12 @@ class ToolIsolation(AppTool, Gerber):
         """
         tool_from_db = deepcopy(tool)
 
-        if tool['data']['tool_target'] != _("Isolation"):
+        if tool['data']['tool_target'] not in [0, 3]:   # [General, Isolation]
+            for idx in range(self.app.ui.plot_tab_area.count()):
+                if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
+                    wdg = self.app.ui.plot_tab_area.widget(idx)
+                    wdg.deleteLater()
+                    self.app.ui.plot_tab_area.removeTab(idx)
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("Selected tool can't be used here. Pick another."))
             return
 
@@ -2667,7 +2766,9 @@ class ToolIsolation(AppTool, Gerber):
             if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
                 self.app.ui.plot_tab_area.setCurrentWidget(self.app.tools_db_tab)
                 break
-        self.app.on_tools_database(source='iso')
+        ret_val = self.app.on_tools_database(source='iso')
+        if ret_val == 'fail':
+            return
         self.app.tools_db_tab.ok_to_add = True
         self.app.tools_db_tab.ui.buttons_frame.hide()
         self.app.tools_db_tab.ui.add_tool_from_db.show()
@@ -3033,7 +3134,7 @@ class IsoUI:
               "this function will not be able to create routing geometry.")
         )
         self.tools_table.horizontalHeaderItem(1).setToolTip(
-            _("Tool Diameter. It's value (in current FlatCAM units)\n"
+            _("Tool Diameter. Its value\n"
               "is the cut width into the material."))
 
         self.tools_table.horizontalHeaderItem(2).setToolTip(
@@ -3098,7 +3199,7 @@ class IsoUI:
 
         self.new_tooldia_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.new_tooldia_entry.set_precision(self.decimals)
-        self.new_tooldia_entry.set_range(0.000, 9999.9999)
+        self.new_tooldia_entry.set_range(0.000, 10000.0000)
         self.new_tooldia_entry.setObjectName("i_new_tooldia")
 
         new_tool_lay.addWidget(self.new_tooldia_entry)
@@ -3119,16 +3220,16 @@ class IsoUI:
 
         bhlay = QtWidgets.QHBoxLayout()
 
-        self.add_newtool_button = FCButton(_('Search and Add'))
-        self.add_newtool_button.setIcon(QtGui.QIcon(self.app.resource_location + '/plus16.png'))
-        self.add_newtool_button.setToolTip(
+        self.search_and_add_btn = FCButton(_('Search and Add'))
+        self.search_and_add_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/plus16.png'))
+        self.search_and_add_btn.setToolTip(
             _("Add a new tool to the Tool Table\n"
               "with the diameter specified above.\n"
               "This is done by a background search\n"
               "in the Tools Database. If nothing is found\n"
               "in the Tools DB then a default tool is added.")
         )
-        bhlay.addWidget(self.add_newtool_button)
+        bhlay.addWidget(self.search_and_add_btn)
 
         self.addtool_from_db_btn = FCButton(_('Pick from DB'))
         self.addtool_from_db_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/search_db32.png'))
@@ -3151,7 +3252,7 @@ class IsoUI:
         self.deltool_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/trash16.png'))
         self.deltool_btn.setToolTip(
             _("Delete a selection of tools in the Tool Table\n"
-              "by first selecting a row(s) in the Tool Table.")
+              "by first selecting a row in the Tool Table.")
         )
         self.grid3.addWidget(self.deltool_btn, 9, 0, 1, 2)
 
@@ -3203,7 +3304,7 @@ class IsoUI:
         # Milling Type Radio Button
         self.milling_type_label = FCLabel('%s:' % _('Milling Type'))
         self.milling_type_label.setToolTip(
-            _("Milling type when the selected tool is of type: 'iso_op':\n"
+            _("Milling type:\n"
               "- climb / best for precision milling and to reduce tool usage\n"
               "- conventional / useful when there is no backlash compensation")
         )
@@ -3211,7 +3312,7 @@ class IsoUI:
         self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
                                             {'label': _('Conventional'), 'value': 'cv'}])
         self.milling_type_radio.setToolTip(
-            _("Milling type when the selected tool is of type: 'iso_op':\n"
+            _("Milling type:\n"
               "- climb / best for precision milling and to reduce tool usage\n"
               "- conventional / useful when there is no backlash compensation")
         )
@@ -3287,11 +3388,11 @@ class IsoUI:
         self.rest_cb.setObjectName("i_rest")
         self.rest_cb.setToolTip(
             _("If checked, use 'rest machining'.\n"
-              "Basically it will isolate outside PCB features,\n"
+              "Basically it will process copper outside PCB features,\n"
               "using the biggest tool and continue with the next tools,\n"
-              "from bigger to smaller, to isolate the copper features that\n"
-              "could not be cleared by previous tool, until there is\n"
-              "no more copper features to isolate or there are no more tools.\n"
+              "from bigger to smaller, to process the copper features that\n"
+              "could not be processed by previous tool, until there is\n"
+              "nothing left to process or there are no more tools.\n\n"
               "If not checked, use the standard algorithm.")
         )
 
@@ -3373,7 +3474,7 @@ class IsoUI:
               "- 'Polygon Selection' -> Isolate a selection of polygons.\n"
               "- 'Reference Object' - will process the area specified by another object.")
         )
-        self.select_combo = FCComboBox()
+        self.select_combo = FCComboBox2()
         self.select_combo.addItems(
             [_("All"), _("Area Selection"), _("Polygon Selection"), _("Reference Object")]
         )
@@ -3382,31 +3483,23 @@ class IsoUI:
         self.grid3.addWidget(self.select_label, 34, 0)
         self.grid3.addWidget(self.select_combo, 34, 1)
 
-        self.reference_combo_type_label = FCLabel('%s:' % _("Ref. Type"))
-        self.reference_combo_type_label.setToolTip(
-            _("The type of FlatCAM object to be used as non copper clearing reference.\n"
-              "It can be Gerber, Excellon or Geometry.")
-        )
-        self.reference_combo_type = FCComboBox()
+        # Reference Type
+        self.reference_combo_type_label = FCLabel('%s:' % _("Type"))
+
+        self.reference_combo_type = FCComboBox2()
         self.reference_combo_type.addItems([_("Gerber"), _("Excellon"), _("Geometry")])
 
         self.grid3.addWidget(self.reference_combo_type_label, 36, 0)
         self.grid3.addWidget(self.reference_combo_type, 36, 1)
 
-        self.reference_combo_label = FCLabel('%s:' % _("Ref. Object"))
-        self.reference_combo_label.setToolTip(
-            _("The FlatCAM object to be used as non copper clearing reference.")
-        )
         self.reference_combo = FCComboBox()
         self.reference_combo.setModel(self.app.collection)
         self.reference_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.reference_combo.is_last = True
 
-        self.grid3.addWidget(self.reference_combo_label, 38, 0)
-        self.grid3.addWidget(self.reference_combo, 38, 1)
+        self.grid3.addWidget(self.reference_combo, 38, 0, 1, 2)
 
         self.reference_combo.hide()
-        self.reference_combo_label.hide()
         self.reference_combo_type.hide()
         self.reference_combo_type_label.hide()
 

+ 26 - 26
appTools/ToolMilling.py

@@ -635,7 +635,7 @@ class ToolMilling(AppTool, Excellon):
         # Get source object.
         try:
             self.excellon_obj = self.app.collection.get_by_name(self.obj_name)
-        except Exception as e:
+        except Exception:
             self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(self.obj_name)))
             return
 
@@ -980,7 +980,7 @@ class ToolMilling(AppTool, Excellon):
         # if the sender is in the column with index 2 then we update the tool_type key
         if cw_col == 2:
             tt = cw.currentText()
-            typ = 'Iso' if tt == 'V' else "Rough"
+            typ = 'Iso' if tt == 'V' else 'Rough'
 
             self.iso_tools[current_uid].update({
                 'type': typ,
@@ -1352,7 +1352,7 @@ class ToolMilling(AppTool, Excellon):
         def job_init(job_obj, app_obj):
             assert job_obj.kind == 'cncjob', "Initializer expected a CNCJobObject, got %s" % type(job_obj)
 
-            app_obj.inform.emit(_("Generating Excellon CNCJob..."))
+            app_obj.inform.emit(_("Generating CNCJob..."))
 
             # get the tool_table items in a list of row items
             tool_table_items = self.get_selected_tools_table_items()
@@ -1674,7 +1674,7 @@ class MillingUI:
               "will be showed as a T1, T2 ... Tn in the Machine Code.\n\n"
               "Here the tools are selected for G-code generation."))
         self.tools_table.horizontalHeaderItem(1).setToolTip(
-            _("Tool Diameter. It's value (in current FlatCAM units) \n"
+            _("Tool Diameter. Its value\n"
               "is the cut width into the material."))
         self.tools_table.horizontalHeaderItem(2).setToolTip(
             _("The number of Drill holes. Holes that are drilled with\n"
@@ -1783,7 +1783,7 @@ class MillingUI:
 
         self.mill_dia_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.mill_dia_entry.set_precision(self.decimals)
-        self.mill_dia_entry.set_range(0.0000, 9999.9999)
+        self.mill_dia_entry.set_range(0.0000, 10000.0000)
         self.mill_dia_entry.setObjectName("e_milling_dia")
 
         self.grid1.addWidget(self.mill_dia_label, 3, 0)
@@ -1800,9 +1800,9 @@ class MillingUI:
         self.cutz_entry.set_precision(self.decimals)
 
         if machinist_setting == 0:
-            self.cutz_entry.set_range(-9999.9999, 0.0000)
+            self.cutz_entry.set_range(-10000.0000, 0.0000)
         else:
-            self.cutz_entry.set_range(-9999.9999, 9999.9999)
+            self.cutz_entry.set_range(-10000.0000, 10000.0000)
 
         self.cutz_entry.setSingleStep(0.1)
         self.cutz_entry.setObjectName("e_cutz")
@@ -1824,7 +1824,7 @@ class MillingUI:
 
         self.maxdepth_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.maxdepth_entry.set_precision(self.decimals)
-        self.maxdepth_entry.set_range(0, 9999.9999)
+        self.maxdepth_entry.set_range(0, 10000.0000)
         self.maxdepth_entry.setSingleStep(0.1)
 
         self.maxdepth_entry.setToolTip(_("Depth of each pass (positive)."))
@@ -1846,9 +1846,9 @@ class MillingUI:
         self.travelz_entry.set_precision(self.decimals)
 
         if machinist_setting == 0:
-            self.travelz_entry.set_range(0.00001, 9999.9999)
+            self.travelz_entry.set_range(0.00001, 10000.0000)
         else:
-            self.travelz_entry.set_range(-9999.9999, 9999.9999)
+            self.travelz_entry.set_range(-10000.0000, 10000.0000)
 
         self.travelz_entry.setSingleStep(0.1)
         self.travelz_entry.setObjectName("e_travelz")
@@ -1864,7 +1864,7 @@ class MillingUI:
         )
         self.xyfeedrate_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.xyfeedrate_entry.set_precision(self.decimals)
-        self.xyfeedrate_entry.set_range(0, 9999.9999)
+        self.xyfeedrate_entry.set_range(0, 10000.0000)
         self.xyfeedrate_entry.setSingleStep(0.1)
         self.xyfeedrate_entry.setObjectName("e_feedratexy")
 
@@ -1881,7 +1881,7 @@ class MillingUI:
         )
         self.feedrate_z_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.feedrate_z_entry.set_precision(self.decimals)
-        self.feedrate_z_entry.set_range(0.0, 99999.9999)
+        self.feedrate_z_entry.set_range(0.0, 910000.0000)
         self.feedrate_z_entry.setSingleStep(0.1)
         self.feedrate_z_entry.setObjectName("e_feedratez")
 
@@ -1899,7 +1899,7 @@ class MillingUI:
         )
         self.feedrate_rapid_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.feedrate_rapid_entry.set_precision(self.decimals)
-        self.feedrate_rapid_entry.set_range(0.0, 99999.9999)
+        self.feedrate_rapid_entry.set_range(0.0, 910000.0000)
         self.feedrate_rapid_entry.setSingleStep(0.1)
         self.feedrate_rapid_entry.setObjectName("e_fr_rapid")
 
@@ -1963,7 +1963,7 @@ class MillingUI:
 
         self.dwelltime_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.dwelltime_entry.set_precision(self.decimals)
-        self.dwelltime_entry.set_range(0.0, 9999.9999)
+        self.dwelltime_entry.set_range(0.0, 10000.0000)
         self.dwelltime_entry.setSingleStep(0.1)
 
         self.dwelltime_entry.setToolTip(
@@ -1986,7 +1986,7 @@ class MillingUI:
 
         self.offset_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.offset_entry.set_precision(self.decimals)
-        self.offset_entry.set_range(-9999.9999, 9999.9999)
+        self.offset_entry.set_range(-10000.0000, 10000.0000)
         self.offset_entry.setObjectName("e_offset")
 
         self.grid1.addWidget(self.tool_offset_label, 25, 0)
@@ -2073,9 +2073,9 @@ class MillingUI:
               "tool change.")
         )
         if machinist_setting == 0:
-            self.toolchangez_entry.set_range(0.0, 9999.9999)
+            self.toolchangez_entry.set_range(0.0, 10000.0000)
         else:
-            self.toolchangez_entry.set_range(-9999.9999, 9999.9999)
+            self.toolchangez_entry.set_range(-10000.0000, 10000.0000)
 
         self.toolchangez_entry.setSingleStep(0.1)
         self.ois_tcz_e = OptionalInputSection(self.toolchange_cb, [self.toolchangez_entry])
@@ -2086,7 +2086,7 @@ class MillingUI:
         # Start move Z:
         self.estartz_label = QtWidgets.QLabel('%s:' % _("Start Z"))
         self.estartz_label.setToolTip(
-            _("Height of the tool just after start.\n"
+            _("Height of the tool just after starting the work.\n"
               "Delete the value if you don't need this feature.")
         )
         self.estartz_entry = NumericalEvalEntry(border_color='#0069A9')
@@ -2104,9 +2104,9 @@ class MillingUI:
         self.endz_entry.set_precision(self.decimals)
 
         if machinist_setting == 0:
-            self.endz_entry.set_range(0.0, 9999.9999)
+            self.endz_entry.set_range(0.0, 10000.0000)
         else:
-            self.endz_entry.set_range(-9999.9999, 9999.9999)
+            self.endz_entry.set_range(-10000.0000, 10000.0000)
 
         self.endz_entry.setSingleStep(0.1)
 
@@ -2134,7 +2134,7 @@ class MillingUI:
 
         self.pdepth_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.pdepth_entry.set_precision(self.decimals)
-        self.pdepth_entry.set_range(-9999.9999, 9999.9999)
+        self.pdepth_entry.set_range(-10000.0000, 10000.0000)
         self.pdepth_entry.setSingleStep(0.1)
         self.pdepth_entry.setObjectName("e_depth_probe")
 
@@ -2152,7 +2152,7 @@ class MillingUI:
 
         self.feedrate_probe_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.feedrate_probe_entry.set_precision(self.decimals)
-        self.feedrate_probe_entry.set_range(0.0, 9999.9999)
+        self.feedrate_probe_entry.set_range(0.0, 10000.0000)
         self.feedrate_probe_entry.setSingleStep(0.1)
         self.feedrate_probe_entry.setObjectName("e_fr_probe")
 
@@ -2163,7 +2163,7 @@ class MillingUI:
         self.feedrate_probe_entry.setVisible(False)
 
         # Preprocessor Excellon selection
-        pp_excellon_label = QtWidgets.QLabel('%s:' % _("Preprocessor E"))
+        pp_excellon_label = QtWidgets.QLabel('%s:' % _("Preprocessor"))
         pp_excellon_label.setToolTip(
             _("The preprocessor JSON file that dictates\n"
               "Gcode output for Excellon Objects.")
@@ -2175,7 +2175,7 @@ class MillingUI:
         self.grid3.addWidget(self.pp_excellon_name_cb, 15, 1)
 
         # Preprocessor Geometry selection
-        pp_geo_label = QtWidgets.QLabel('%s:' % _("Preprocessor G"))
+        pp_geo_label = QtWidgets.QLabel('%s:' % _("Preprocessor"))
         pp_geo_label.setToolTip(
             _("The preprocessor JSON file that dictates\n"
               "Gcode output for Geometry (Milling) Objects.")
@@ -2250,14 +2250,14 @@ class MillingUI:
         self.over_z_label.setToolTip(_("The height Z to which the tool will rise in order to avoid\n"
                                        "an interdiction area."))
         self.over_z_entry = FCDoubleSpinner()
-        self.over_z_entry.set_range(0.000, 9999.9999)
+        self.over_z_entry.set_range(0.000, 10000.0000)
         self.over_z_entry.set_precision(self.decimals)
 
         grid_a1.addWidget(self.over_z_label, 2, 0)
         grid_a1.addWidget(self.over_z_entry, 2, 1)
 
         # Button Add Area
-        self.add_area_button = QtWidgets.QPushButton(_('Add area:'))
+        self.add_area_button = QtWidgets.QPushButton(_('Add Area:'))
         self.add_area_button.setToolTip(_("Add an Exclusion Area."))
 
         # Area Selection shape

+ 8 - 8
appTools/ToolMove.py

@@ -137,7 +137,7 @@ class ToolMove(AppTool):
                 else:
                     self.point2 = copy(self.point1)
                     self.point1 = pos
-                self.app.inform.emit(_("MOVE: Click on the Destination point ..."))
+                self.app.inform.emit(_("Click on the DESTINATION point ..."))
 
             if self.clicked_move == 1:
                 try:
@@ -160,10 +160,11 @@ class ToolMove(AppTool):
                                 if obj.options['plot'] and obj.visible is True]
 
                     def job_move(app_obj):
-                        with self.app.proc_container.new(_("Moving...")) as proc:
+                        with self.app.proc_container.new(_("Moving ...")):
 
                             if not obj_list:
-                                app_obj.app.inform.emit('[WARNING_NOTCL] %s' % _("No object(s) selected."))
+                                app_obj.app.inform.emit('[ERROR_NOTCL] %s %s' % (_("Failed."),
+                                                                                 _("No object is selected.")))
                                 return "fail"
 
                             try:
@@ -206,8 +207,8 @@ class ToolMove(AppTool):
 
                         # delete the selection bounding box
                         self.delete_shape()
-                        self.app.inform.emit('[success] %s %s' %
-                                             (str(sel_obj.kind).capitalize(), 'object was moved ...'))
+                        self.app.inform.emit('[success] %s %s ...' %
+                                             (str(sel_obj.kind).capitalize(), _('object was moved')))
 
                     self.app.worker_task.emit({'fcn': job_move, 'params': [self]})
 
@@ -217,8 +218,7 @@ class ToolMove(AppTool):
 
                 except TypeError as e:
                     log.debug("ToolMove.on_left_click() --> %s" % str(e))
-                    self.app.inform.emit('[ERROR_NOTCL] ToolMove.on_left_click() --> %s' %
-                                         _('Error when mouse left click.'))
+                    self.app.inform.emit('[ERROR_NOTCL] ToolMove. %s' % _('Error when mouse left click.'))
                     return
 
             self.clicked_move = 1
@@ -226,7 +226,7 @@ class ToolMove(AppTool):
     def replot(self, obj_list):
 
         def worker_task():
-            with self.app.proc_container.new('%s...' % _("Plotting")):
+            with self.app.proc_container.new('%s ...' % _("Plotting")):
                 for sel_obj in obj_list:
                     sel_obj.plot()
 

+ 282 - 185
appTools/ToolNCC.py

@@ -8,8 +8,8 @@
 from PyQt5 import QtWidgets, QtCore, QtGui
 
 from appTool import AppTool
-from appGUI.GUIElements import FCCheckBox, FCDoubleSpinner, RadioSet, FCTable, FCInputDialog, FCButton,\
-    FCComboBox, OptionalInputSection, FCLabel
+from appGUI.GUIElements import FCCheckBox, FCDoubleSpinner, RadioSet, FCTable, FCButton,\
+    FCComboBox, OptionalInputSection, FCLabel, FCInputDialogSpinnerButton, FCComboBox2
 from appParsers.ParseGerber import Gerber
 
 from camlib import grace
@@ -451,20 +451,35 @@ class NonCopperClear(AppTool, Gerber):
         self.blockSignals(False)
 
     def on_add_tool_by_key(self):
-        tool_add_popup = FCInputDialog(title='%s...' % _("New Tool"),
-                                       text='%s:' % _('Enter a Tool Diameter'),
-                                       min=0.0001, max=9999.9999, decimals=self.decimals)
+        # tool_add_popup = FCInputDialog(title='%s...' % _("New Tool"),
+        #                                text='%s:' % _('Enter a Tool Diameter'),
+        #                                min=0.0001, max=10000.0000, decimals=self.decimals)
+        btn_icon = QtGui.QIcon(self.app.resource_location + '/open_excellon32.png')
+
+        tool_add_popup = FCInputDialogSpinnerButton(title='%s...' % _("New Tool"),
+                                                    text='%s:' % _('Enter a Tool Diameter'),
+                                                    min=0.0001, max=10000.0000, decimals=self.decimals,
+                                                    button_icon=btn_icon,
+                                                    callback=self.on_find_optimal_tooldia,
+                                                    parent=self.app.ui)
         tool_add_popup.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/letter_t_32.png'))
 
-        val, ok = tool_add_popup.get_value()
+        def find_optimal(valor):
+            tool_add_popup.set_value(float(valor))
+
+        self.optimal_found_sig.connect(find_optimal)
+
+        val, ok = tool_add_popup.get_results()
         if ok:
             if float(val) == 0:
                 self.app.inform.emit('[WARNING_NOTCL] %s' %
                                      _("Please enter a tool diameter with non-zero value, in Float format."))
+                self.optimal_found_sig.disconnect(find_optimal)
                 return
             self.on_tool_add(custom_dia=float(val))
         else:
             self.app.inform.emit('[WARNING_NOTCL] %s...' % _("Adding Tool cancelled"))
+        self.optimal_found_sig.disconnect(find_optimal)
 
     def set_tool_ui(self):
         self.units = self.app.defaults['units'].upper()
@@ -615,7 +630,7 @@ class NonCopperClear(AppTool, Gerber):
         # read the table tools uid
         current_uid_list = []
         for row in range(self.ui.tools_table.rowCount()):
-            uid = int(self.ui.tools_table.item(row,3).text())
+            uid = int(self.ui.tools_table.item(row, 3).text())
             current_uid_list.append(uid)
 
         new_tools = {}
@@ -661,7 +676,8 @@ class NonCopperClear(AppTool, Gerber):
                     tool_id += 1
 
                     id_ = QtWidgets.QTableWidgetItem('%d' % int(tool_id))
-                    id_.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+                    flags = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
+                    id_.setFlags(flags)
                     row_no = tool_id - 1
                     self.ui.tools_table.setItem(row_no, 0, id_)  # Tool name/id
 
@@ -682,8 +698,8 @@ class NonCopperClear(AppTool, Gerber):
 
         # make the diameter column editable
         for row in range(tool_id):
-            self.ui.tools_table.item(row, 1).setFlags(
-                QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+            flags = QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
+            self.ui.tools_table.item(row, 1).setFlags(flags)
 
         self.ui.tools_table.resizeColumnsToContents()
         self.ui.tools_table.resizeRowsToContents()
@@ -801,9 +817,7 @@ class NonCopperClear(AppTool, Gerber):
         obj_type = self.ui.reference_combo_type.currentIndex()
         self.ui.reference_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
         self.ui.reference_combo.setCurrentIndex(0)
-        self.ui.reference_combo.obj_type = {
-            _("Gerber"): "Gerber", _("Excellon"): "Excellon", _("Geometry"): "Geometry"
-        }[self.ui.reference_combo_type.get_value()]
+        self.ui.reference_combo.obj_type = {0: "Gerber", 1: "Excellon", 2: "Geometry"}[obj_type]
 
     def on_order_changed(self, order):
         if order != 'no':
@@ -823,7 +837,7 @@ class NonCopperClear(AppTool, Gerber):
         # if the sender is in the column with index 2 then we update the tool_type key
         if cw_col == 2:
             tt = cw.currentText()
-            typ = 'Iso' if tt == 'V' else "Rough"
+            typ = 'Iso' if tt == 'V' else 'Rough'
 
             self.ncc_tools[current_uid].update({
                 'type': typ,
@@ -831,10 +845,59 @@ class NonCopperClear(AppTool, Gerber):
             })
 
     def on_find_optimal_tooldia(self):
-        self.find_safe_tooldia_worker(is_displayed=True)
+        self.find_safe_tooldia_worker()
+
+    @staticmethod
+    def find_optim_mp(aperture_storage, decimals):
+        msg = 'ok'
+        total_geo = []
+
+        for ap in list(aperture_storage.keys()):
+            if 'geometry' in aperture_storage[ap]:
+                for geo_el in aperture_storage[ap]['geometry']:
+                    if 'solid' in geo_el and geo_el['solid'] is not None and geo_el['solid'].is_valid:
+                        total_geo.append(geo_el['solid'])
+
+        total_geo = MultiPolygon(total_geo)
+        total_geo = total_geo.buffer(0)
+
+        try:
+            __ = iter(total_geo)
+            geo_len = len(total_geo)
+        except TypeError:
+            msg = ('[ERROR_NOTCL] %s' % _("The Gerber object has one Polygon as geometry.\n"
+                                          "There are no distances between geometry elements to be found."))
+
+        min_dict = {}
+        idx = 1
+        for geo in total_geo:
+            for s_geo in total_geo[idx:]:
+                # minimize the number of distances by not taking into considerations
+                # those that are too small
+                dist = geo.distance(s_geo)
+                dist = float('%.*f' % (decimals, dist))
+                loc_1, loc_2 = nearest_points(geo, s_geo)
+
+                proc_loc = (
+                    (float('%.*f' % (decimals, loc_1.x)), float('%.*f' % (decimals, loc_1.y))),
+                    (float('%.*f' % (decimals, loc_2.x)), float('%.*f' % (decimals, loc_2.y)))
+                )
+
+                if dist in min_dict:
+                    min_dict[dist].append(proc_loc)
+                else:
+                    min_dict[dist] = [proc_loc]
+
+            idx += 1
+
+        min_list = list(min_dict.keys())
+        min_dist = min(min_list)
+
+        return msg, min_dist
 
-    def find_safe_tooldia_worker(self, is_displayed):
-        self.app.inform.emit(_("NCC Tool. Checking tools for validity."))
+    # multiprocessing variant
+    def find_safe_tooldia_multiprocessing(self):
+        self.app.inform.emit(_("Checking tools for validity."))
         self.units = self.app.defaults['units'].upper()
 
         obj_name = self.ui.object_combo.currentText()
@@ -850,82 +913,24 @@ class NonCopperClear(AppTool, Gerber):
             self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(obj_name)))
             return
 
-        proc = self.app.proc_container.new(_("Working..."))
-
-        def job_thread(app_obj, is_display):
-            try:
-                old_disp_number = 0
-                pol_nr = 0
-                app_obj.proc_container.update_view_text(' %d%%' % 0)
-                total_geo = []
-
-                for ap in list(fcobj.apertures.keys()):
-                    if 'geometry' in fcobj.apertures[ap]:
-                        for geo_el in fcobj.apertures[ap]['geometry']:
-                            if self.app.abort_flag:
-                                # graceful abort requested by the user
-                                raise grace
+        def job_thread(app_obj):
+            with self.app.proc_container.new(_("Checking ...")):
 
-                            if 'solid' in geo_el and geo_el['solid'] is not None and geo_el['solid'].is_valid:
-                                total_geo.append(geo_el['solid'])
+                ap_storage = fcobj.apertures
 
-                total_geo = MultiPolygon(total_geo)
-                total_geo = total_geo.buffer(0)
+                p = app_obj.pool.apply_async(self.find_optim_mp, args=(ap_storage, self.decimals))
+                res = p.get()
 
-                try:
-                    __ = iter(total_geo)
-                    geo_len = len(total_geo)
-                    geo_len = (geo_len * (geo_len - 1)) / 2
-                except TypeError:
-                    app_obj.inform.emit('[ERROR_NOTCL] %s' %
-                                        _("The Gerber object has one Polygon as geometry.\n"
-                                          "There are no distances between geometry elements to be found."))
+                if res[0] != 'ok':
+                    app_obj.inform.emit(res[0])
                     return 'fail'
+                else:
+                    min_dist = res[1]
 
-                min_dict = {}
-                idx = 1
-                for geo in total_geo:
-                    for s_geo in total_geo[idx:]:
-                        if self.app.abort_flag:
-                            # graceful abort requested by the user
-                            raise grace
-
-                        # minimize the number of distances by not taking into considerations those that are too small
-                        dist = geo.distance(s_geo)
-                        dist = float('%.*f' % (self.decimals, dist))
-                        loc_1, loc_2 = nearest_points(geo, s_geo)
-
-                        proc_loc = (
-                            (float('%.*f' % (self.decimals, loc_1.x)), float('%.*f' % (self.decimals, loc_1.y))),
-                            (float('%.*f' % (self.decimals, loc_2.x)), float('%.*f' % (self.decimals, loc_2.y)))
-                        )
-
-                        if dist in min_dict:
-                            min_dict[dist].append(proc_loc)
-                        else:
-                            min_dict[dist] = [proc_loc]
-
-                        pol_nr += 1
-                        disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
-
-                        if old_disp_number < disp_number <= 100:
-                            app_obj.proc_container.update_view_text(' %d%%' % disp_number)
-                            old_disp_number = disp_number
-                    idx += 1
-
-                min_list = list(min_dict.keys())
-                min_dist = min(min_list)
-
-                min_dist_truncated = self.app.dec_format(float(min_dist), self.decimals)
-                self.safe_tooldia = min_dist_truncated
-
-                if is_display:
-                    self.optimal_found_sig.emit(min_dist_truncated)
+                try:
+                    min_dist_truncated = self.app.dec_format(float(min_dist), self.decimals)
+                    self.safe_tooldia = min_dist_truncated
 
-                    app_obj.inform.emit('[success] %s: %s %s' %
-                                        (_("Optimal tool diameter found"), str(min_dist_truncated),
-                                         self.units.lower()))
-                else:
                     # find the selected tool ID's
                     sorted_tools = []
                     table_items = self.ui.tools_table.selectedItems()
@@ -954,16 +959,113 @@ class NonCopperClear(AppTool, Gerber):
 
                     # reset the value to prepare for another isolation
                     self.safe_tooldia = None
-            except Exception as ee:
-                log.debug(str(ee))
-                return
+                except Exception as ee:
+                    log.debug(str(ee))
+                    return
+
+        self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
 
-        self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app, is_displayed]})
+    def find_safe_tooldia_worker(self):
+        self.app.inform.emit(_("Checking tools for validity."))
+        self.units = self.app.defaults['units'].upper()
+
+        obj_name = self.ui.object_combo.currentText()
+
+        # Get source object.
+        try:
+            fcobj = self.app.collection.get_by_name(obj_name)
+        except Exception:
+            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(obj_name)))
+            return
+
+        if fcobj is None:
+            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(obj_name)))
+            return
+
+        def job_thread(app_obj):
+            with self.app.proc_container.new(_("Checking ...")):
+                try:
+                    old_disp_number = 0
+                    pol_nr = 0
+                    app_obj.proc_container.update_view_text(' %d%%' % 0)
+                    total_geo = []
+
+                    for ap in list(fcobj.apertures.keys()):
+                        if 'geometry' in fcobj.apertures[ap]:
+                            for geo_el in fcobj.apertures[ap]['geometry']:
+                                if self.app.abort_flag:
+                                    # graceful abort requested by the user
+                                    raise grace
+
+                                if 'solid' in geo_el and geo_el['solid'] is not None and geo_el['solid'].is_valid:
+                                    total_geo.append(geo_el['solid'])
+
+                    total_geo = MultiPolygon(total_geo)
+                    total_geo = total_geo.buffer(0)
+
+                    try:
+                        __ = iter(total_geo)
+                        geo_len = len(total_geo)
+                        geo_len = (geo_len * (geo_len - 1)) / 2
+                    except TypeError:
+                        app_obj.inform.emit('[ERROR_NOTCL] %s' %
+                                            _("The Gerber object has one Polygon as geometry.\n"
+                                              "There are no distances between geometry elements to be found."))
+                        return 'fail'
+
+                    min_dict = {}
+                    idx = 1
+                    for geo in total_geo:
+                        for s_geo in total_geo[idx:]:
+                            if self.app.abort_flag:
+                                # graceful abort requested by the user
+                                raise grace
+
+                            # minimize the number of distances by not taking into considerations
+                            # those that are too small
+                            dist = geo.distance(s_geo)
+                            dist = float('%.*f' % (self.decimals, dist))
+                            loc_1, loc_2 = nearest_points(geo, s_geo)
+
+                            proc_loc = (
+                                (float('%.*f' % (self.decimals, loc_1.x)), float('%.*f' % (self.decimals, loc_1.y))),
+                                (float('%.*f' % (self.decimals, loc_2.x)), float('%.*f' % (self.decimals, loc_2.y)))
+                            )
+
+                            if dist in min_dict:
+                                min_dict[dist].append(proc_loc)
+                            else:
+                                min_dict[dist] = [proc_loc]
+
+                            pol_nr += 1
+                            disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
+
+                            if old_disp_number < disp_number <= 100:
+                                app_obj.proc_container.update_view_text(' %d%%' % disp_number)
+                                old_disp_number = disp_number
+                        idx += 1
+
+                    min_list = list(min_dict.keys())
+                    min_dist = min(min_list)
+
+                    min_dist_truncated = self.app.dec_format(float(min_dist), self.decimals)
+                    self.safe_tooldia = min_dist_truncated
+
+                    self.optimal_found_sig.emit(min_dist_truncated)
+
+                    app_obj.inform.emit('[success] %s: %s %s' %
+                                        (_("Optimal tool diameter found"), str(min_dist_truncated),
+                                         self.units.lower()))
+                except Exception as ee:
+                    log.debug(str(ee))
+                    return
+
+        self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
 
     def on_tool_add(self, custom_dia=None):
         self.blockSignals(True)
 
-        filename = self.app.data_path + '\\tools_db.FlatDB'
+        filename = self.app.tools_database_path()
 
         new_tools_dict = deepcopy(self.default_data)
         updated_tooldia = None
@@ -1008,7 +1110,7 @@ class NonCopperClear(AppTool, Gerber):
                 tools = f.read()
         except IOError:
             self.app.log.error("Could not load tools DB file.")
-            self.app.inform.emit('[ERROR] %s' % _("Could not load Tools DB file."))
+            self.app.inform.emit('[ERROR] %s' % _("Could not load the file."))
             self.blockSignals(False)
             self.on_tool_default_add(dia=tool_dia)
             return
@@ -1029,7 +1131,7 @@ class NonCopperClear(AppTool, Gerber):
 
         offset = 'Path'
         offset_val = 0.0
-        typ = "Rough"
+        typ = 'Rough'
         tool_type = 'V'
         # look in database tools
         for db_tool, db_tool_val in tools_db_dict.items():
@@ -1313,7 +1415,8 @@ class NonCopperClear(AppTool, Gerber):
             return
 
         if self.ui.valid_cb.get_value() is True:
-            self.find_safe_tooldia_worker(is_displayed=False)
+            # this is done in another Process
+            self.find_safe_tooldia_multiprocessing()
 
         # use the selected tools in the tool table; get diameters for isolation
         self.iso_dia_list = []
@@ -1348,7 +1451,7 @@ class NonCopperClear(AppTool, Gerber):
         self.o_name = '%s_ncc' % self.obj_name
 
         self.select_method = self.ui.select_combo.get_value()
-        if self.select_method == _('Itself'):
+        if self.select_method == 0:   # Itself
             self.bound_obj_name = self.ui.object_combo.currentText()
             # Get source object.
             try:
@@ -1362,7 +1465,7 @@ class NonCopperClear(AppTool, Gerber):
                               isotooldia=self.iso_dia_list,
                               outname=self.o_name,
                               tools_storage=self.ncc_tools)
-        elif self.select_method == _("Area Selection"):
+        elif self.select_method == 1:   # Area Selection
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the start point of the area."))
 
             if self.app.is_legacy is False:
@@ -1381,7 +1484,7 @@ class NonCopperClear(AppTool, Gerber):
             # disconnect flags
             self.area_sel_disconnect_flag = True
 
-        elif self.select_method == _("Reference Object"):
+        elif self.select_method == 2:   # Reference Object
             self.bound_obj_name = self.ui.reference_combo.currentText()
             # Get source object.
             try:
@@ -1664,7 +1767,7 @@ class NonCopperClear(AppTool, Gerber):
         box_kind = box_obj.kind if box_obj is not None else None
 
         env_obj = None
-        if ncc_select == _('Itself'):
+        if ncc_select == 0:     # _('Itself')
             geo_n = ncc_obj.solid_geometry
 
             try:
@@ -1680,13 +1783,13 @@ class NonCopperClear(AppTool, Gerber):
                 log.debug("NonCopperClear.calculate_bounding_box() 'itself'  --> %s" % str(e))
                 self.app.inform.emit('[ERROR_NOTCL] %s' % _("No object available."))
                 return None
-        elif ncc_select == _("Area Selection"):
+        elif ncc_select == 1:   # _("Area Selection")
             env_obj = unary_union(self.sel_rect)
             try:
                 __ = iter(env_obj)
             except Exception:
                 env_obj = [env_obj]
-        elif ncc_select == _("Reference Object"):
+        elif ncc_select == 2:   # _("Reference Object")
             if box_obj is None:
                 return None, None
 
@@ -1728,14 +1831,14 @@ class NonCopperClear(AppTool, Gerber):
             return 'fail'
 
         new_bounding_box = None
-        if ncc_select == _('Itself'):
+        if ncc_select == 0:     # _('Itself')
             try:
                 new_bounding_box = bbox.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre)
             except Exception as e:
                 log.debug("NonCopperClear.apply_margin_to_bounding_box() 'itself'  --> %s" % str(e))
                 self.app.inform.emit('[ERROR_NOTCL] %s' % _("No object available."))
                 return 'fail'
-        elif ncc_select == _("Area Selection"):
+        elif ncc_select == 1:   # _("Area Selection")
             geo_buff_list = []
             for poly in bbox:
                 if self.app.abort_flag:
@@ -1743,7 +1846,7 @@ class NonCopperClear(AppTool, Gerber):
                     raise grace
                 geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre))
             new_bounding_box = unary_union(geo_buff_list)
-        elif ncc_select == _("Reference Object"):
+        elif ncc_select == 2:   # _("Reference Object")
             if box_kind == 'geometry':
                 geo_buff_list = []
                 for poly in bbox:
@@ -1962,7 +2065,7 @@ class NonCopperClear(AppTool, Gerber):
 
         cp = None
 
-        if ncc_method == _("Standard"):
+        if ncc_method == 0:     # standard
             try:
                 cp = self.clear_polygon(pol, tooldia,
                                         steps_per_circle=self.circle_steps,
@@ -1973,7 +2076,7 @@ class NonCopperClear(AppTool, Gerber):
                 return "fail"
             except Exception as ee:
                 log.debug("NonCopperClear.clear_polygon_worker() Standard --> %s" % str(ee))
-        elif ncc_method == _("Seed"):
+        elif ncc_method == 1:   # seed
             try:
                 cp = self.clear_polygon2(pol, tooldia,
                                          steps_per_circle=self.circle_steps,
@@ -1984,7 +2087,7 @@ class NonCopperClear(AppTool, Gerber):
                 return "fail"
             except Exception as ee:
                 log.debug("NonCopperClear.clear_polygon_worker() Seed --> %s" % str(ee))
-        elif ncc_method == _("Lines"):
+        elif ncc_method == 2:   # Lines
             try:
                 cp = self.clear_polygon3(pol, tooldia,
                                          steps_per_circle=self.circle_steps,
@@ -1995,7 +2098,7 @@ class NonCopperClear(AppTool, Gerber):
                 return "fail"
             except Exception as ee:
                 log.debug("NonCopperClear.clear_polygon_worker() Lines --> %s" % str(ee))
-        elif ncc_method == _("Combo"):
+        elif ncc_method == 3:   # Combo
             try:
                 self.app.inform.emit(_("Clearing the polygon with the method: lines."))
                 cp = self.clear_polygon3(pol, tooldia,
@@ -2064,9 +2167,9 @@ class NonCopperClear(AppTool, Gerber):
         log.debug("Executing the handler ...")
 
         if run_threaded:
-            proc = self.app.proc_container.new(_("Non-Copper clearing ..."))
+            proc = self.app.proc_container.new('%s...' % _("Non-Copper Clearing"))
         else:
-            self.app.proc_container.view.set_busy(_("Non-Copper clearing ..."))
+            self.app.proc_container.view.set_busy('%s...' % _("Non-Copper Clearing"))
             QtWidgets.QApplication.processEvents()
 
         # ######################################################################################################
@@ -2121,7 +2224,7 @@ class NonCopperClear(AppTool, Gerber):
 
             app_obj.poly_not_cleared = False    # flag for polygons not cleared
 
-            if ncc_select == _("Reference Object"):
+            if ncc_select == 2:     # Reference Object
                 bbox_geo, bbox_kind = self.calculate_bounding_box(
                     ncc_obj=ncc_obj, box_obj=sel_obj, ncc_select=ncc_select)
             else:
@@ -2249,11 +2352,11 @@ class NonCopperClear(AppTool, Gerber):
 
                         # check if there is a geometry at all in the cleared geometry
                         if cleared_geo:
+                            formatted_tool = self.app.dec_format(tool, self.decimals)
                             # find the tooluid associated with the current tool_dia so we know where to add the tool
                             # solid_geometry
                             for k, v in tools_storage.items():
-                                if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals,
-                                                                                                    tool)):
+                                if self.app.dec_format(v['tooldia'], self.decimals) == formatted_tool:
                                     current_uid = int(k)
 
                                     # add the solid_geometry to the current too in self.paint_tools dictionary
@@ -2348,14 +2451,10 @@ class NonCopperClear(AppTool, Gerber):
 
             sorted_clear_tools.sort(reverse=True)
 
-            cleared_by_last_tool = []
-            rest_geo = []
-            current_uid = 1
-
-            # repurposed flag for final object, geo_obj. True if it has any solid_geometry, False if not.
+            # re purposed flag for final object, geo_obj. True if it has any solid_geometry, False if not.
             app_obj.poly_not_cleared = True
 
-            if ncc_select == _("Reference Object"):
+            if ncc_select == 2:     # Reference Object
                 env_obj, box_obj_kind = self.calculate_bounding_box(
                     ncc_obj=ncc_obj, box_obj=sel_obj, ncc_select=ncc_select)
             else:
@@ -2652,9 +2751,9 @@ class NonCopperClear(AppTool, Gerber):
         :return:
         """
         if run_threaded:
-            proc = self.app.proc_container.new(_("Non-Copper clearing ..."))
+            proc = self.app.proc_container.new('%s...' % _("Non-Copper Clearing"))
         else:
-            self.app.proc_container.view.set_busy(_("Non-Copper clearing ..."))
+            self.app.proc_container.view.set_busy('%s...' % _("Non-Copper Clearing"))
             QtWidgets.QApplication.processEvents()
 
         # #####################################################################
@@ -2706,7 +2805,7 @@ class NonCopperClear(AppTool, Gerber):
         self.app.inform.emit(_("NCC Tool. Preparing non-copper polygons."))
 
         try:
-            if sel_obj is None or sel_obj == _('Itself'):
+            if sel_obj is None or sel_obj == 0:     # sel_obj == 'itself'
                 ncc_sel_obj = ncc_obj
             else:
                 ncc_sel_obj = sel_obj
@@ -2715,7 +2814,7 @@ class NonCopperClear(AppTool, Gerber):
             return 'fail'
 
         bounding_box = None
-        if ncc_select == _('Itself'):
+        if ncc_select == 0:     # itself
             geo_n = ncc_sel_obj.solid_geometry
 
             try:
@@ -2734,7 +2833,7 @@ class NonCopperClear(AppTool, Gerber):
                 self.app.inform.emit('[ERROR_NOTCL] %s' % _("No object available."))
                 return 'fail'
 
-        elif ncc_select == 'area':
+        elif ncc_select == 1:   # area
             geo_n = unary_union(self.sel_rect)
             try:
                 __ = iter(geo_n)
@@ -2751,7 +2850,7 @@ class NonCopperClear(AppTool, Gerber):
 
             bounding_box = unary_union(geo_buff_list)
 
-        elif ncc_select == _("Reference Object"):
+        elif ncc_select == 2:   # Reference Object
             geo_n = ncc_sel_obj.solid_geometry
             if ncc_sel_obj.kind == 'geometry':
                 try:
@@ -2822,10 +2921,10 @@ class NonCopperClear(AppTool, Gerber):
             # Generate area for each tool
             offset_a = sum(sorted_tools)
             current_uid = int(1)
-            try:
-                tool = eval(self.app.defaults["tools_ncc_tools"])[0]
-            except TypeError:
-                tool = eval(self.app.defaults["tools_ncc_tools"])
+            # try:
+            #     tool = eval(self.app.defaults["tools_ncc_tools"])[0]
+            # except TypeError:
+            #     tool = eval(self.app.defaults["tools_ncc_tools"])
 
             # ###################################################################################################
             # Calculate the empty area by subtracting the solid_geometry from the object bounding box geometry ##
@@ -2840,6 +2939,8 @@ class NonCopperClear(AppTool, Gerber):
                     sol_geo = ncc_obj.solid_geometry.buffer(0)
                 else:
                     sol_geo = ncc_obj.solid_geometry
+                    if isinstance(sol_geo, list):
+                        sol_geo = unary_union(sol_geo)
 
                 if has_offset is True:
                     app_obj.inform.emit('[WARNING_NOTCL] %s ...' % _("Buffering"))
@@ -2984,6 +3085,7 @@ class NonCopperClear(AppTool, Gerber):
             log.debug("NCC Tool. Finished calculation of 'empty' area.")
             self.app.inform.emit(_("NCC Tool. Finished calculation of 'empty' area."))
 
+            tool = 1
             # COPPER CLEARING #
             for tool in sorted_tools:
                 log.debug("Starting geometry processing for tool: %s" % str(tool))
@@ -3040,13 +3142,13 @@ class NonCopperClear(AppTool, Gerber):
                                 try:
                                     for pol in p:
                                         if pol is not None and isinstance(pol, Polygon):
-                                            if ncc_method == 'standard':
+                                            if ncc_method == 0:    # standard
                                                 cp = self.clear_polygon(pol, tool,
                                                                         self.circle_steps,
                                                                         overlap=overlap, contour=contour,
                                                                         connect=connect,
                                                                         prog_plot=False)
-                                            elif ncc_method == 'seed':
+                                            elif ncc_method == 1:  # seed
                                                 cp = self.clear_polygon2(pol, tool,
                                                                          self.circle_steps,
                                                                          overlap=overlap, contour=contour,
@@ -3069,11 +3171,11 @@ class NonCopperClear(AppTool, Gerber):
                                                         "It is: %s" % str(type(pol)))
                                 except TypeError:
                                     if isinstance(p, Polygon):
-                                        if ncc_method == 'standard':
+                                        if ncc_method == 0:    # standard
                                             cp = self.clear_polygon(p, tool, self.circle_steps,
                                                                     overlap=overlap, contour=contour, connect=connect,
                                                                     prog_plot=False)
-                                        elif ncc_method == 'seed':
+                                        elif ncc_method == 1:   # seed
                                             cp = self.clear_polygon2(p, tool, self.circle_steps,
                                                                      overlap=overlap, contour=contour, connect=connect,
                                                                      prog_plot=False)
@@ -3436,12 +3538,12 @@ class NonCopperClear(AppTool, Gerber):
 
                                 if isinstance(p, Polygon):
                                     try:
-                                        if ncc_method == 'standard':
+                                        if ncc_method == 0:     # standard
                                             cp = self.clear_polygon(p, tool_used,
                                                                     self.circle_steps,
                                                                     overlap=overlap, contour=contour, connect=connect,
                                                                     prog_plot=False)
-                                        elif ncc_method == 'seed':
+                                        elif ncc_method == 1:   # seed
                                             cp = self.clear_polygon2(p, tool_used,
                                                                      self.circle_steps,
                                                                      overlap=overlap, contour=contour, connect=connect,
@@ -3465,13 +3567,13 @@ class NonCopperClear(AppTool, Gerber):
                                             QtWidgets.QApplication.processEvents()
 
                                             try:
-                                                if ncc_method == 'standard':
+                                                if ncc_method == 0:     # 'standard'
                                                     cp = self.clear_polygon(poly_p, tool_used,
                                                                             self.circle_steps,
                                                                             overlap=overlap, contour=contour,
                                                                             connect=connect,
                                                                             prog_plot=False)
-                                                elif ncc_method == 'seed':
+                                                elif ncc_method == 1:   # 'seed'
                                                     cp = self.clear_polygon2(poly_p, tool_used,
                                                                              self.circle_steps,
                                                                              overlap=overlap, contour=contour,
@@ -3622,6 +3724,10 @@ class NonCopperClear(AppTool, Gerber):
             geo_len = 1
         else:
             geo_len = len(target)
+
+        if isinstance(target, list):
+            target = MultiPolygon(target)
+
         pol_nr = 0
         old_disp_number = 0
 
@@ -3705,7 +3811,12 @@ class NonCopperClear(AppTool, Gerber):
         """
         tool_from_db = deepcopy(tool)
 
-        if tool['data']['tool_target'] != _("NCC"):
+        if tool['data']['tool_target'] not in [0, 5]:   # [General, NCC]
+            for idx in range(self.app.ui.plot_tab_area.count()):
+                if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
+                    wdg = self.app.ui.plot_tab_area.widget(idx)
+                    wdg.deleteLater()
+                    self.app.ui.plot_tab_area.removeTab(idx)
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("Selected tool can't be used here. Pick another."))
             return
 
@@ -3795,7 +3906,9 @@ class NonCopperClear(AppTool, Gerber):
             if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
                 self.app.ui.plot_tab_area.setCurrentWidget(self.app.tools_db_tab)
                 break
-        self.app.on_tools_database(source='ncc')
+        ret_val = self.app.on_tools_database(source='ncc')
+        if ret_val == 'fail':
+            return
         self.app.tools_db_tab.ok_to_add = True
         self.app.tools_db_tab.ui.buttons_frame.hide()
         self.app.tools_db_tab.ui.add_tool_from_db.show()
@@ -3896,7 +4009,7 @@ class NccUI:
               "this function will not be able to create painting geometry.")
         )
         self.tools_table.horizontalHeaderItem(1).setToolTip(
-            _("Tool Diameter. It's value (in current FlatCAM units)\n"
+            _("Tool Diameter. Its value\n"
               "is the cut width into the material."))
 
         self.tools_table.horizontalHeaderItem(2).setToolTip(
@@ -3973,7 +4086,7 @@ class NccUI:
 
         self.new_tooldia_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.new_tooldia_entry.set_precision(self.decimals)
-        self.new_tooldia_entry.set_range(0.000, 9999.9999)
+        self.new_tooldia_entry.set_range(0.000, 10000.0000)
         self.new_tooldia_entry.setObjectName(_("Tool Dia"))
 
         new_tool_lay.addWidget(self.new_tooldia_entry)
@@ -4025,7 +4138,7 @@ class NccUI:
         self.deltool_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/trash16.png'))
         self.deltool_btn.setToolTip(
             _("Delete a selection of tools in the Tool Table\n"
-              "by first selecting a row(s) in the Tool Table.")
+              "by first selecting a row in the Tool Table.")
         )
         self.grid3.addWidget(self.deltool_btn, 9, 0, 1, 2)
 
@@ -4067,7 +4180,7 @@ class NccUI:
         # Milling Type Radio Button
         self.milling_type_label = FCLabel('%s:' % _('Milling Type'))
         self.milling_type_label.setToolTip(
-            _("Milling type when the selected tool is of type: 'iso_op':\n"
+            _("Milling type:\n"
               "- climb / best for precision milling and to reduce tool usage\n"
               "- conventional / useful when there is no backlash compensation")
         )
@@ -4075,7 +4188,7 @@ class NccUI:
         self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
                                             {'label': _('Conventional'), 'value': 'cv'}])
         self.milling_type_radio.setToolTip(
-            _("Milling type when the selected tool is of type: 'iso_op':\n"
+            _("Milling type:\n"
               "- climb / best for precision milling and to reduce tool usage\n"
               "- conventional / useful when there is no backlash compensation")
         )
@@ -4092,8 +4205,8 @@ class NccUI:
         self.nccoverlabel.setToolTip(
             _("How much (percentage) of the tool width to overlap each tool pass.\n"
               "Adjust the value starting with lower values\n"
-              "and increasing it if areas that should be cleared are still \n"
-              "not cleared.\n"
+              "and increasing it if areas that should be processed are still \n"
+              "not processed.\n"
               "Lower values = faster processing, faster execution on CNC.\n"
               "Higher values = slow processing and slow execution on CNC\n"
               "due of too many paths.")
@@ -4122,7 +4235,7 @@ class NccUI:
         #     {"label": _("Straight lines"), "value": "lines"}
         # ], orientation='vertical', stretch=False)
 
-        self.ncc_method_combo = FCComboBox()
+        self.ncc_method_combo = FCComboBox2()
         self.ncc_method_combo.addItems(
             [_("Standard"), _("Seed"), _("Lines"), _("Combo")]
         )
@@ -4138,7 +4251,7 @@ class NccUI:
         )
         self.ncc_margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.ncc_margin_entry.set_precision(self.decimals)
-        self.ncc_margin_entry.set_range(-9999.9999, 9999.9999)
+        self.ncc_margin_entry.set_range(-10000.0000, 10000.0000)
         self.ncc_margin_entry.setObjectName("n_margin")
 
         self.grid3.addWidget(self.nccmarginlabel, 17, 0)
@@ -4171,8 +4284,7 @@ class NccUI:
         self.ncc_choice_offset_cb.setToolTip(
             _("If used, it will add an offset to the copper features.\n"
               "The copper clearing will finish to a distance\n"
-              "from the copper features.\n"
-              "The value can be between 0 and 10 FlatCAM units.")
+              "from the copper features.")
         )
         self.grid3.addWidget(self.ncc_choice_offset_cb, 19, 0)
 
@@ -4224,11 +4336,11 @@ class NccUI:
 
         self.ncc_rest_cb.setToolTip(
             _("If checked, use 'rest machining'.\n"
-              "Basically it will clear copper outside PCB features,\n"
+              "Basically it will process copper outside PCB features,\n"
               "using the biggest tool and continue with the next tools,\n"
-              "from bigger to smaller, to clear areas of copper that\n"
-              "could not be cleared by previous tool, until there is\n"
-              "no more copper to clear or there are no more tools.\n"
+              "from bigger to smaller, to process the copper features that\n"
+              "could not be processed by previous tool, until there is\n"
+              "nothing left to process or there are no more tools.\n\n"
               "If not checked, use the standard algorithm.")
         )
 
@@ -4241,7 +4353,7 @@ class NccUI:
         )
         self.rest_ncc_margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.rest_ncc_margin_entry.set_precision(self.decimals)
-        self.rest_ncc_margin_entry.set_range(-9999.9999, 9999.9999)
+        self.rest_ncc_margin_entry.set_range(-10000.0000, 10000.0000)
         self.rest_ncc_margin_entry.setObjectName("n_margin")
 
         self.grid3.addWidget(self.rest_nccmarginlabel, 26, 0)
@@ -4268,8 +4380,7 @@ class NccUI:
         self.rest_ncc_choice_offset_cb.setToolTip(
             _("If used, it will add an offset to the copper features.\n"
               "The copper clearing will finish to a distance\n"
-              "from the copper features.\n"
-              "The value can be between 0 and 10 FlatCAM units.")
+              "from the copper features.")
         )
         self.grid3.addWidget(self.rest_ncc_choice_offset_cb, 28, 0)
 
@@ -4289,13 +4400,8 @@ class NccUI:
 
         self.rest_ois_ncc_offset = OptionalInputSection(self.rest_ncc_choice_offset_cb, [self.rest_ncc_offset_spinner])
 
-        # ## Reference
-        # self.select_radio = RadioSet([
-        #     {'label': _('Itself'), 'value': 'itself'},
-        #     {"label": _("Area Selection"), "value": "area"},
-        #     {'label': _("Reference Object"), 'value': 'box'}
-        # ], orientation='vertical', stretch=False)
-        self.select_combo = FCComboBox()
+        # Reference Selection Combo
+        self.select_combo = FCComboBox2()
         self.select_combo.addItems(
             [_("Itself"), _("Area Selection"), _("Reference Object")]
         )
@@ -4311,31 +4417,25 @@ class NccUI:
         self.grid3.addWidget(self.select_label, 29, 0, )
         self.grid3.addWidget(self.select_combo, 29, 1)
 
-        form1 = QtWidgets.QFormLayout()
-        self.grid3.addLayout(form1, 30, 0, 1, 2)
-
-        self.reference_combo_type_label = FCLabel('%s:' % _("Ref. Type"))
+        self.reference_combo_type_label = FCLabel('%s:' % _("Type"))
         self.reference_combo_type_label.setToolTip(
             _("The type of FlatCAM object to be used as non copper clearing reference.\n"
               "It can be Gerber, Excellon or Geometry.")
         )
-        self.reference_combo_type = FCComboBox()
+        self.reference_combo_type = FCComboBox2()
         self.reference_combo_type.addItems([_("Gerber"), _("Excellon"), _("Geometry")])
 
-        form1.addRow(self.reference_combo_type_label, self.reference_combo_type)
+        self.grid3.addWidget(self.reference_combo_type_label, 31, 0, )
+        self.grid3.addWidget(self.reference_combo_type, 31, 1)
 
-        self.reference_combo_label = FCLabel('%s:' % _("Ref. Object"))
-        self.reference_combo_label.setToolTip(
-            _("The FlatCAM object to be used as non copper clearing reference.")
-        )
         self.reference_combo = FCComboBox()
         self.reference_combo.setModel(self.app.collection)
         self.reference_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.reference_combo.is_last = True
-        form1.addRow(self.reference_combo_label, self.reference_combo)
+
+        self.grid3.addWidget(self.reference_combo, 33, 0, 1, 2)
 
         self.reference_combo.hide()
-        self.reference_combo_label.hide()
         self.reference_combo_type.hide()
         self.reference_combo_type_label.hide()
 
@@ -4348,8 +4448,8 @@ class NccUI:
         self.area_shape_radio = RadioSet([{'label': _("Square"), 'value': 'square'},
                                           {'label': _("Polygon"), 'value': 'polygon'}])
 
-        self.grid3.addWidget(self.area_shape_label, 31, 0)
-        self.grid3.addWidget(self.area_shape_radio, 31, 1)
+        self.grid3.addWidget(self.area_shape_label, 35, 0)
+        self.grid3.addWidget(self.area_shape_radio, 35, 1)
 
         self.area_shape_label.hide()
         self.area_shape_radio.hide()
@@ -4362,12 +4462,12 @@ class NccUI:
         )
         self.valid_cb.setObjectName("n_check")
 
-        self.grid3.addWidget(self.valid_cb, 33, 0, 1, 2)
+        self.grid3.addWidget(self.valid_cb, 37, 0, 1, 2)
 
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        self.grid3.addWidget(separator_line, 35, 0, 1, 2)
+        self.grid3.addWidget(separator_line, 39, 0, 1, 2)
 
         self.generate_ncc_button = FCButton(_('Generate Geometry'))
         self.generate_ncc_button.setIcon(QtGui.QIcon(self.app.resource_location + '/geometry32.png'))
@@ -4433,9 +4533,8 @@ class NccUI:
     def on_toggle_reference(self):
         sel_combo = self.select_combo.get_value()
 
-        if sel_combo == _("Itself"):
+        if sel_combo == 0:  # itself
             self.reference_combo.hide()
-            self.reference_combo_label.hide()
             self.reference_combo_type.hide()
             self.reference_combo_type_label.hide()
             self.area_shape_label.hide()
@@ -4443,9 +4542,8 @@ class NccUI:
 
             # disable rest-machining for area painting
             self.ncc_rest_cb.setDisabled(False)
-        elif sel_combo == _("Area Selection"):
+        elif sel_combo == 1:    # area selection
             self.reference_combo.hide()
-            self.reference_combo_label.hide()
             self.reference_combo_type.hide()
             self.reference_combo_type_label.hide()
             self.area_shape_label.show()
@@ -4456,7 +4554,6 @@ class NccUI:
             # self.ncc_rest_cb.setDisabled(True)
         else:
             self.reference_combo.show()
-            self.reference_combo_label.show()
             self.reference_combo_type.show()
             self.reference_combo_type_label.show()
             self.area_shape_label.hide()

+ 2 - 1
appTools/ToolOptimal.py

@@ -142,7 +142,7 @@ class ToolOptimal(AppTool):
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber objects can be evaluated."))
             return
 
-        proc = self.app.proc_container.new(_("Working..."))
+        proc = self.app.proc_container.new(_("Working ..."))
 
         def job_thread(app_obj):
             app_obj.inform.emit(_("Optimal Tool. Started to search for the minimum distance between copper features."))
@@ -573,6 +573,7 @@ class OptimalUI:
 
         # GO button
         self.calculate_button = FCButton(_("Find Minimum"))
+        self.calculate_button.setIcon(QtGui.QIcon(self.app.resource_location + '/open_excellon32.png'))
         self.calculate_button.setToolTip(
             _("Calculate the minimum distance between copper features,\n"
               "this will allow the determination of the right tool to\n"

+ 14 - 9
appTools/ToolPDF.py

@@ -109,9 +109,10 @@ class ToolPDF(AppTool):
         short_name = filename.split('/')[-1].split('\\')[-1]
         self.parsing_promises.append(short_name)
 
-        self.pdf_parsed[short_name] = {}
-        self.pdf_parsed[short_name]['pdf'] = {}
-        self.pdf_parsed[short_name]['filename'] = filename
+        self.pdf_parsed[short_name] = {
+            'pdf': {},
+            'filename': filename
+        }
 
         self.pdf_decompressed[short_name] = ''
 
@@ -119,7 +120,7 @@ class ToolPDF(AppTool):
             # graceful abort requested by the user
             raise grace
 
-        with self.app.proc_container.new(_("Parsing PDF file ...")):
+        with self.app.proc_container.new(_("Parsing ...")):
             with open(filename, "rb") as f:
                 pdf = f.read()
 
@@ -181,22 +182,26 @@ class ToolPDF(AppTool):
             name_tool = 0
             for dia in sorted_dia:
                 name_tool += 1
+                tool = str(name_tool)
 
-                # create tools dictionary
-                spec = {"C": dia, 'solid_geometry': []}
-                exc_obj.tools[str(name_tool)] = spec
+                exc_obj.tools[tool] = {
+                    'tooldia': dia,
+                    'drills': [],
+                    'solid_geometry': []
+                }
 
-                # create drill list of dictionaries
+                # update the drill list
                 for dia_points in points:
                     if dia == dia_points:
                         for pt in points[dia_points]:
-                            exc_obj.drills.append({'point': Point(pt), 'tool': str(name_tool)})
+                            exc_obj.tools[tool]['drills'].append(Point(pt))
                         break
 
             ret = exc_obj.create_geometry()
             if ret == 'fail':
                 log.debug("Could not create geometry for Excellon object.")
                 return "fail"
+
             for tool in exc_obj.tools:
                 if exc_obj.tools[tool]['solid_geometry']:
                     return

+ 93 - 110
appTools/ToolPaint.py

@@ -13,8 +13,8 @@ from copy import deepcopy
 
 from appParsers.ParseGerber import Gerber
 from camlib import Geometry, FlatCAMRTreeStorage, grace
-from appGUI.GUIElements import FCTable, FCDoubleSpinner, FCCheckBox, FCInputDialog, RadioSet, FCButton, FCComboBox, \
-    FCLabel
+from appGUI.GUIElements import FCTable, FCDoubleSpinner, FCCheckBox, FCInputDoubleSpinner, RadioSet, \
+    FCButton, FCComboBox, FCLabel, FCComboBox2
 
 from shapely.geometry import base, Polygon, MultiPolygon, LinearRing, Point
 from shapely.ops import unary_union, linemerge
@@ -156,16 +156,14 @@ class ToolPaint(AppTool, Gerber):
             self.ui.paintmethod_combo.model().item(idx).setEnabled(True)
         else:
             self.ui.paintmethod_combo.model().item(idx).setEnabled(False)
-            if self.ui.paintmethod_combo.get_value() == _("Laser_lines"):
-                self.ui.paintmethod_combo.set_value(_("Lines"))
+            if self.ui.paintmethod_combo.get_value() == idx:    # if its Laser Lines
+                self.ui.paintmethod_combo.set_value(idx+1)
 
     def on_reference_combo_changed(self):
         obj_type = self.ui.reference_type_combo.currentIndex()
         self.ui.reference_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
         self.ui.reference_combo.setCurrentIndex(0)
-        self.ui.reference_combo.obj_type = {
-            _("Gerber"): "Gerber", _("Excellon"): "Excellon", _("Geometry"): "Geometry"
-        }[self.ui.reference_type_combo.get_value()]
+        self.ui.reference_combo.obj_type = {0: "Gerber", 1: "Excellon", 2: "Geometry"}[obj_type]
 
     def connect_signals_at_init(self):
         # #############################################################################
@@ -383,10 +381,11 @@ class ToolPaint(AppTool, Gerber):
         self.blockSignals(False)
 
     def on_add_tool_by_key(self):
-        tool_add_popup = FCInputDialog(title='%s...' % _("New Tool"),
-                                       text='%s:' % _('Enter a Tool Diameter'),
-                                       min=0.0000, max=99.9999, decimals=4)
-        tool_add_popup.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/letter_t_32.png'))
+        tool_add_popup = FCInputDoubleSpinner(title='%s...' % _("New Tool"),
+                                              text='%s:' % _('Enter a Tool Diameter'),
+                                              min=0.0000, max=99.9999, decimals=self.decimals,
+                                              parent=self.app.ui)
+        tool_add_popup.set_icon(QtGui.QIcon(self.app.resource_location + '/letter_t_32.png'))
 
         val, ok = tool_add_popup.get_value()
         if ok:
@@ -413,7 +412,7 @@ class ToolPaint(AppTool, Gerber):
         # if the sender is in the column with index 2 then we update the tool_type key
         if cw_col == 2:
             tt = cw.currentText()
-            typ = 'Iso' if tt == 'V' else "Rough"
+            typ = 'Iso' if tt == 'V' else 'Rough'
 
             self.paint_tools[current_uid].update({
                 'type': typ,
@@ -542,10 +541,20 @@ class ToolPaint(AppTool, Gerber):
 
         self.ui.on_rest_machining_check(state=self.app.defaults["tools_paint_rest"])
 
-        # if the Paint Method is "Single" disable the tool table context menu
-        if self.default_data["tools_paint_selectmethod"] == "single":
+        # if the Paint Method is "Polygon Selection" disable the tool table context menu
+        if self.default_data["tools_paint_selectmethod"] == 1:
             self.ui.tools_table.setContextMenuPolicy(Qt.NoContextMenu)
 
+        # make sure that we can't get selection of Laser Lines for Geometry even if it's set in the Preferences
+        # because we don't select the default object type in Preferences but here
+        idx = self.ui.paintmethod_combo.findText(_("Laser_lines"))
+        if self.ui.type_obj_radio.get_value().lower() == 'gerber':
+            self.ui.paintmethod_combo.model().item(idx).setEnabled(True)
+        else:
+            self.ui.paintmethod_combo.model().item(idx).setEnabled(False)
+            if self.ui.paintmethod_combo.get_value() == idx:  # if its Laser Lines
+                self.ui.paintmethod_combo.set_value(idx + 1)
+
         self.ui.tools_table.drag_drop_sig.connect(self.rebuild_ui)
 
     def rebuild_ui(self):
@@ -658,7 +667,7 @@ class ToolPaint(AppTool, Gerber):
     def on_tool_add(self, custom_dia=None):
         self.blockSignals(True)
 
-        filename = self.app.data_path + '\\tools_db.FlatDB'
+        filename = self.app.tools_database_path()
 
         new_tools_dict = deepcopy(self.default_data)
         updated_tooldia = None
@@ -701,7 +710,7 @@ class ToolPaint(AppTool, Gerber):
                 tools = f.read()
         except IOError:
             self.app.log.error("Could not load tools DB file.")
-            self.app.inform.emit('[ERROR] %s' % _("Could not load Tools DB file."))
+            self.app.inform.emit('[ERROR] %s' % _("Could not load the file."))
             self.blockSignals(False)
             self.on_tool_default_add(dia=tool_dia)
             return
@@ -721,7 +730,7 @@ class ToolPaint(AppTool, Gerber):
 
         offset = 'Path'
         offset_val = 0.0
-        typ = "Rough"
+        typ = 'Rough'
         tool_type = 'V'
         # look in database tools
         for db_tool, db_tool_val in tools_db_dict.items():
@@ -986,7 +995,7 @@ class ToolPaint(AppTool, Gerber):
 
         self.sel_rect = []
 
-        obj_type = self.ui.type_obj_radio.get_value
+        obj_type = self.ui.type_obj_radio.get_value()
         self.circle_steps = int(self.app.defaults["gerber_circle_steps"]) if obj_type == 'gerber' else \
             int(self.app.defaults["geometry_circle_steps"])
         self.obj_name = self.ui.obj_combo.currentText()
@@ -996,16 +1005,16 @@ class ToolPaint(AppTool, Gerber):
             self.paint_obj = self.app.collection.get_by_name(str(self.obj_name))
         except Exception as e:
             log.debug("ToolPaint.on_paint_button_click() --> %s" % str(e))
-            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object: %s"), self.obj_name))
+            self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), self.obj_name))
             return
 
         if self.paint_obj is None:
             self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), self.paint_obj))
             return
 
-        # test if the Geometry Object is multigeo and return Fail if True because
-        # for now Paint don't work on MultiGeo
-        if self.paint_obj.kind == 'geometry' and self.paint_obj.multigeo is True:
+        # test if the Geometry Object is multigeo with more than one tool and return Fail if True because
+        # for now Paint don't work on MultiGeo with more than one tools
+        if self.paint_obj.kind == 'geometry' and self.paint_obj.multigeo is True and len(self.paint_obj.tools) > 1:
             self.app.inform.emit('[ERROR_NOTCL] %s...' % _("Can't do Paint on MultiGeo geometries"))
             return 'Fail'
 
@@ -1032,10 +1041,10 @@ class ToolPaint(AppTool, Gerber):
             return
 
         self.select_method = self.ui.selectmethod_combo.get_value()
-        if self.select_method == _("All"):
+        if self.select_method == 0:  # _("All")
             self.paint_poly_all(self.paint_obj, tooldia=self.tooldia_list, outname=self.o_name)
 
-        elif self.select_method == _("Polygon Selection"):
+        elif self.select_method == 1:   # _("Polygon Selection")
             # disengage the grid snapping since it may be hard to click on polygons with grid snapping on
             if self.app.ui.grid_snap_btn.isChecked():
                 self.grid_status_memory = True
@@ -1058,8 +1067,8 @@ class ToolPaint(AppTool, Gerber):
             # disconnect flags
             self.poly_sel_disconnect_flag = True
 
-        elif self.select_method == _("Area Selection"):
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the start point of the paint area."))
+        elif self.select_method == 2:   # _("Area Selection")
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the start point of the area."))
 
             if self.app.is_legacy is False:
                 self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
@@ -1077,7 +1086,7 @@ class ToolPaint(AppTool, Gerber):
             # disconnect flags
             self.area_sel_disconnect_flag = True
 
-        elif self.select_method == _("Reference Object"):
+        elif self.select_method == 3:   # _("Reference Object")
             self.bound_obj_name = self.reference_combo.currentText()
             # Get source object.
             try:
@@ -1125,7 +1134,7 @@ class ToolPaint(AppTool, Gerber):
                     self.app.inform.emit(
                         '%s: %d. %s' % (_("Added polygon"),
                                         int(len(self.poly_dict)),
-                                        _("Click to add next polygon or right click to start painting."))
+                                        _("Click to add next polygon or right click to start."))
                     )
                 else:
                     try:
@@ -1138,7 +1147,7 @@ class ToolPaint(AppTool, Gerber):
                         return
                     self.app.inform.emit(
                         '%s. %s' % (_("Removed polygon"),
-                                    _("Click to add/remove next polygon or right click to start painting."))
+                                    _("Click to add/remove next polygon or right click to start."))
                     )
 
                 self.app.tool_shapes.redraw()
@@ -1453,7 +1462,7 @@ class ToolPaint(AppTool, Gerber):
 
         cpoly = None
 
-        if paint_method == _("Standard"):
+        if paint_method == 0:   # _("Standard")
             try:
                 # Type(cp) == FlatCAMRTreeStorage | None
                 cpoly = self.clear_polygon(polyg,
@@ -1467,7 +1476,7 @@ class ToolPaint(AppTool, Gerber):
                 return "fail"
             except Exception as ee:
                 log.debug("ToolPaint.paint_polygon_worker() Standard --> %s" % str(ee))
-        elif paint_method == _("Seed"):
+        elif paint_method == 1:  # _("Seed")
             try:
                 # Type(cp) == FlatCAMRTreeStorage | None
                 cpoly = self.clear_polygon2(polyg,
@@ -1481,7 +1490,7 @@ class ToolPaint(AppTool, Gerber):
                 return "fail"
             except Exception as ee:
                 log.debug("ToolPaint.paint_polygon_worker() Seed --> %s" % str(ee))
-        elif paint_method == _("Lines"):
+        elif paint_method == 2:  # _("Lines")
             try:
                 # Type(cp) == FlatCAMRTreeStorage | None
                 cpoly = self.clear_polygon3(polyg,
@@ -1495,7 +1504,7 @@ class ToolPaint(AppTool, Gerber):
                 return "fail"
             except Exception as ee:
                 log.debug("ToolPaint.paint_polygon_worker() Lines --> %s" % str(ee))
-        elif paint_method == _("Laser_lines"):
+        elif paint_method == 3:  # _("Laser_lines")
             try:
                 # line = None
                 # aperture_size = None
@@ -1544,7 +1553,7 @@ class ToolPaint(AppTool, Gerber):
 
                 # process the flashes found in the selected polygon with the 'lines' method for rectangular
                 # flashes and with _("Seed") for oblong and circular flashes
-                # and pads (flahes) need the contour therefore I override the GUI settings with always True
+                # and pads (flashes) need the contour therefore I override the GUI settings with always True
                 for ap_type in flash_el_dict:
                     for elem in flash_el_dict[ap_type]:
                         if 'solid' in elem:
@@ -1558,7 +1567,7 @@ class ToolPaint(AppTool, Gerber):
                                                           connect=conn,
                                                           prog_plot=prog_plot)
                                 pads_lines_list += [p for p in f_o.get_objects() if p]
-
+                            # this is the same as above but I keep it in case I will modify something in the future
                             elif ap_type == 'O':
                                 f_o = self.clear_polygon2(elem['solid'],
                                                           tooldia=tooldiameter,
@@ -1646,7 +1655,7 @@ class ToolPaint(AppTool, Gerber):
                 return "fail"
             except Exception as ee:
                 log.debug("ToolPaint.paint_polygon_worker() Laser Lines --> %s" % str(ee))
-        elif paint_method == _("Combo"):
+        elif paint_method == 4:  # _("Combo")
             try:
                 self.app.inform.emit(_("Painting polygon with method: lines."))
                 cpoly = self.clear_polygon3(polyg,
@@ -1851,7 +1860,7 @@ class ToolPaint(AppTool, Gerber):
                 except Exception as e:
                     log.debug("Could not Paint the polygons. %s" % str(e))
                     mssg = '[ERROR] %s\n%s' % (_("Could not do Paint. Try a different combination of parameters. "
-                                                 "Or a different strategy of paint"), str(e))
+                                                 "Or a different method of Paint"), str(e))
                     self.app.inform.emit(mssg)
                     continue
 
@@ -2060,7 +2069,7 @@ class ToolPaint(AppTool, Gerber):
                 except Exception as e:
                     log.debug("Could not Paint the polygons. %s" % str(e))
                     msg = '[ERROR] %s\n%s' % (_("Could not do Paint. Try a different combination of parameters. "
-                                                "Or a different strategy of paint"), str(e))
+                                                "Or a different method of Paint"), str(e))
                     self.app.inform.emit(msg)
                     continue
 
@@ -2173,18 +2182,18 @@ class ToolPaint(AppTool, Gerber):
             proc.done()
 
             if ret == 'fail':
-                self.app.inform.emit('[ERROR] %s' % _("Paint failed."))
+                self.app.inform.emit('[ERROR] %s' % _("Failed."))
                 return
 
             # focus on Properties Tab
             # self.app.ui.notebook.setCurrentWidget(self.app.ui.properties_tab)
 
-            self.app.inform.emit('[success] %s' % _("Paint Done."))
+            self.app.inform.emit('[success] %s' % _("Done."))
 
         # Promise object with the new name
         self.app.collection.promise(name)
 
-        proc = self.app.proc_container.new(_("Painting..."))
+        proc = self.app.proc_container.new(_("Painting ..."))
 
         if run_threaded:
             # Background
@@ -2234,7 +2243,9 @@ class ToolPaint(AppTool, Gerber):
             self.app.inform.emit('%s %s' % (_("Paint Tool."), _("Normal painting polygon task started.")))
 
         if inside_pt and poly_list is None:
-            polygon_list = [self.find_polygon(point=inside_pt, geoset=obj.solid_geometry)]
+            polygon_list = self.find_polygon(point=inside_pt, geoset=obj.solid_geometry)
+            if polygon_list:
+                polygon_list = [polygon_list]
         elif (inside_pt is None and poly_list) or (inside_pt and poly_list):
             polygon_list = poly_list
         else:
@@ -2244,7 +2255,7 @@ class ToolPaint(AppTool, Gerber):
         if polygon_list is None:
             self.app.log.warning('No polygon found.')
             self.app.inform.emit('[WARNING] %s' % _('No polygon found.'))
-            return
+            return "fail"
 
         self.paint_geo(obj, polygon_list, tooldia=tooldia, order=order, method=method, outname=outname,
                        tools_storage=tools_storage, plot=plot, run_threaded=run_threaded)
@@ -2616,7 +2627,12 @@ class ToolPaint(AppTool, Gerber):
         """
         tool_from_db = deepcopy(tool)
 
-        if tool['data']['tool_target'] != _("Paint"):
+        if tool['data']['tool_target'] not in [0, 4]:   # [General, Paint]
+            for idx in range(self.app.ui.plot_tab_area.count()):
+                if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
+                    wdg = self.app.ui.plot_tab_area.widget(idx)
+                    wdg.deleteLater()
+                    self.app.ui.plot_tab_area.removeTab(idx)
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("Selected tool can't be used here. Pick another."))
             return
 
@@ -2664,7 +2680,7 @@ class ToolPaint(AppTool, Gerber):
             max_uid = max(tool_uid_list)
         tooluid = max_uid + 1
 
-        tooldia = float('%.*f' % (self.decimals, tooldia))
+        tooldia = self.app.dec_format(tooldia, self.decimals)
 
         tool_dias = []
         for k, v in self.paint_tools.items():
@@ -2715,7 +2731,9 @@ class ToolPaint(AppTool, Gerber):
             if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
                 self.app.ui.plot_tab_area.setCurrentWidget(self.app.tools_db_tab)
                 break
-        self.app.on_tools_database(source='paint')
+        ret_val = self.app.on_tools_database(source='paint')
+        if ret_val == 'fail':
+            return
         self.app.tools_db_tab.ok_to_add = True
         self.app.tools_db_tab.ui.buttons_frame.hide()
         self.app.tools_db_tab.ui.add_tool_from_db.show()
@@ -2824,7 +2842,7 @@ class PaintUI:
               "this function will not be able to create painting geometry.")
         )
         self.tools_table.horizontalHeaderItem(1).setToolTip(
-            _("Tool Diameter. It's value (in current FlatCAM units) \n"
+            _("Tool Diameter. Its value\n"
               "is the cut width into the material."))
 
         self.tools_table.horizontalHeaderItem(2).setToolTip(
@@ -2887,7 +2905,7 @@ class PaintUI:
         )
         self.new_tooldia_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.new_tooldia_entry.set_precision(self.decimals)
-        self.new_tooldia_entry.set_range(0.000, 9999.9999)
+        self.new_tooldia_entry.set_range(0.000, 10000.0000)
         self.new_tooldia_entry.setObjectName('p_tool_dia')
 
         self.grid3.addWidget(self.new_tooldia_lbl, 2, 0)
@@ -2927,7 +2945,7 @@ class PaintUI:
         self.deltool_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/trash16.png'))
         self.deltool_btn.setToolTip(
             _("Delete a selection of tools in the Tool Table\n"
-              "by first selecting a row(s) in the Tool Table.")
+              "by first selecting a row in the Tool Table.")
         )
         self.grid3.addWidget(self.deltool_btn, 9, 0, 1, 2)
 
@@ -2956,8 +2974,8 @@ class PaintUI:
         ovlabel.setToolTip(
             _("How much (percentage) of the tool width to overlap each tool pass.\n"
               "Adjust the value starting with lower values\n"
-              "and increasing it if areas that should be painted are still \n"
-              "not painted.\n"
+              "and increasing it if areas that should be processed are still \n"
+              "not processed.\n"
               "Lower values = faster processing, faster execution on CNC.\n"
               "Higher values = slow processing and slow execution on CNC\n"
               "due of too many paths.")
@@ -2981,7 +2999,7 @@ class PaintUI:
         )
         self.offset_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.offset_entry.set_precision(self.decimals)
-        self.offset_entry.set_range(-9999.9999, 9999.9999)
+        self.offset_entry.set_range(-10000.0000, 10000.0000)
         self.offset_entry.setObjectName('p_offset')
 
         grid4.addWidget(self.offset_label, 2, 0)
@@ -2999,19 +3017,8 @@ class PaintUI:
               "- Combo: In case of failure a new method will be picked from the above\n"
               "in the order specified.")
         )
-        # self.paintmethod_combo = RadioSet([
-        #     {"label": _("Standard"), "value": "standard"},
-        #     {"label": _("Seed-based"), "value": _("Seed")},
-        #     {"label": _("Straight lines"), "value": _("Lines")},
-        #     {"label": _("Laser lines"), "value": _("Laser_lines")},
-        #     {"label": _("Combo"), "value": _("Combo")}
-        # ], orientation='vertical', stretch=False)
-
-        # for choice in self.paintmethod_combo.choices:
-        #     if choice['value'] == _("Laser_lines"):
-        #         choice["radio"].setEnabled(False)
-
-        self.paintmethod_combo = FCComboBox()
+
+        self.paintmethod_combo = FCComboBox2()
         self.paintmethod_combo.addItems(
             [_("Standard"), _("Seed"), _("Lines"), _("Laser_lines"), _("Combo")]
         )
@@ -3070,11 +3077,11 @@ class PaintUI:
         self.rest_cb.setObjectName('p_rest_machining')
         self.rest_cb.setToolTip(
             _("If checked, use 'rest machining'.\n"
-              "Basically it will clear copper outside PCB features,\n"
+              "Basically it will process copper outside PCB features,\n"
               "using the biggest tool and continue with the next tools,\n"
-              "from bigger to smaller, to clear areas of copper that\n"
-              "could not be cleared by previous tool, until there is\n"
-              "no more copper to clear or there are no more tools.\n\n"
+              "from bigger to smaller, to process the copper features that\n"
+              "could not be processed by previous tool, until there is\n"
+              "nothing left to process or there are no more tools.\n\n"
               "If not checked, use the standard algorithm.")
         )
         grid4.addWidget(self.rest_cb, 16, 0, 1, 2)
@@ -3088,7 +3095,7 @@ class PaintUI:
         )
         self.rest_offset_entry = FCDoubleSpinner(callback=self.confirmation_message)
         self.rest_offset_entry.set_precision(self.decimals)
-        self.rest_offset_entry.set_range(-9999.9999, 9999.9999)
+        self.rest_offset_entry.set_range(-10000.0000, 10000.0000)
 
         grid4.addWidget(self.rest_offset_label, 17, 0)
         grid4.addWidget(self.rest_offset_entry, 17, 1)
@@ -3104,58 +3111,36 @@ class PaintUI:
               "- 'Reference Object' - will process the area specified by another object.")
         )
 
-        # grid3 = QtWidgets.QGridLayout()
-        # self.selectmethod_combo = RadioSet([
-        #     {"label": _("Polygon Selection"), "value": "single"},
-        #     {"label": _("Area Selection"), "value": "area"},
-        #     {"label": _("All Polygons"), "value": "all"},
-        #     {"label": _("Reference Object"), "value": "ref"}
-        # ], orientation='vertical', stretch=False)
-        # self.selectmethod_combo.setObjectName('p_selection')
-        # self.selectmethod_combo.setToolTip(
-        #     _("How to select Polygons to be painted.\n"
-        #       "- 'Polygon Selection' - left mouse click to add/remove polygons to be painted.\n"
-        #       "- 'Area Selection' - left mouse click to start selection of the area to be painted.\n"
-        #       "Keeping a modifier key pressed (CTRL or SHIFT) will allow to add multiple areas.\n"
-        #       "- 'All Polygons' - the Paint will start after click.\n"
-        #       "- 'Reference Object' - will do non copper clearing within the area\n"
-        #       "specified by another object.")
-        # )
-
-        self.selectmethod_combo = FCComboBox()
+        self.selectmethod_combo = FCComboBox2()
         self.selectmethod_combo.addItems(
-            [_("Polygon Selection"), _("Area Selection"), _("All"), _("Reference Object")]
+            [_("All"), _("Polygon Selection"), _("Area Selection"), _("Reference Object")]
         )
         self.selectmethod_combo.setObjectName('p_selection')
 
         grid4.addWidget(selectlabel, 18, 0)
         grid4.addWidget(self.selectmethod_combo, 18, 1)
 
-        form1 = QtWidgets.QFormLayout()
-        grid4.addLayout(form1, 20, 0, 1, 2)
-
-        self.reference_type_label = FCLabel('%s:' % _("Ref. Type"))
+        # Type of Reference Object
+        self.reference_type_label = FCLabel('%s:' % _("Type"))
         self.reference_type_label.setToolTip(
             _("The type of FlatCAM object to be used as paint reference.\n"
               "It can be Gerber, Excellon or Geometry.")
         )
-        self.reference_type_combo = FCComboBox()
+        self.reference_type_combo = FCComboBox2()
         self.reference_type_combo.addItems([_("Gerber"), _("Excellon"), _("Geometry")])
 
-        form1.addRow(self.reference_type_label, self.reference_type_combo)
+        grid4.addWidget(self.reference_type_label, 20, 0)
+        grid4.addWidget(self.reference_type_combo, 20, 1)
 
-        self.reference_combo_label = FCLabel('%s:' % _("Ref. Object"))
-        self.reference_combo_label.setToolTip(
-            _("The FlatCAM object to be used as non copper clearing reference.")
-        )
+        # Reference Object
         self.reference_combo = FCComboBox()
         self.reference_combo.setModel(self.app.collection)
         self.reference_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.reference_combo.is_last = True
-        form1.addRow(self.reference_combo_label, self.reference_combo)
+
+        grid4.addWidget(self.reference_combo, 22, 0, 1, 2)
 
         self.reference_combo.hide()
-        self.reference_combo_label.hide()
         self.reference_type_combo.hide()
         self.reference_type_label.hide()
 
@@ -3168,8 +3153,8 @@ class PaintUI:
         self.area_shape_radio = RadioSet([{'label': _("Square"), 'value': 'square'},
                                           {'label': _("Polygon"), 'value': 'polygon'}])
 
-        grid4.addWidget(self.area_shape_label, 21, 0)
-        grid4.addWidget(self.area_shape_radio, 21, 1)
+        grid4.addWidget(self.area_shape_label, 24, 0)
+        grid4.addWidget(self.area_shape_radio, 24, 1)
 
         self.area_shape_label.hide()
         self.area_shape_radio.hide()
@@ -3229,31 +3214,29 @@ class PaintUI:
     def on_selection(self):
         sel_combo = self.selectmethod_combo.get_value()
 
-        if sel_combo == _("Reference Object"):
+        if sel_combo == 3:  # _("Reference Object")
             self.reference_combo.show()
-            self.reference_combo_label.show()
             self.reference_type_combo.show()
             self.reference_type_label.show()
         else:
             self.reference_combo.hide()
-            self.reference_combo_label.hide()
             self.reference_type_combo.hide()
             self.reference_type_label.hide()
 
-        if sel_combo == _("Polygon Selection"):
+        if sel_combo == 1:  # _("Polygon Selection")
             # disable rest-machining for single polygon painting
             # self.ui.rest_cb.set_value(False)
             # self.ui.rest_cb.setDisabled(True)
             pass
 
-        if sel_combo == _("Area Selection"):
+        if sel_combo == 2:  # _("Area Selection") index 2 in combobox (FCComboBox2() returns index instead of text)
             # disable rest-machining for area painting
             # self.ui.rest_cb.set_value(False)
             # self.ui.rest_cb.setDisabled(True)
 
             self.area_shape_label.show()
             self.area_shape_radio.show()
-        else:
+        else:   # All = index 0 in combobox
             self.new_tooldia_entry.setDisabled(False)
             self.add_newtool_button.setDisabled(False)
             self.deltool_btn.setDisabled(False)

+ 28 - 29
appTools/ToolPanelize.py

@@ -8,7 +8,8 @@
 from PyQt5 import QtWidgets, QtGui, QtCore
 from appTool import AppTool
 
-from appGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet, FCCheckBox, OptionalInputSection, FCComboBox
+from appGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet, FCCheckBox, OptionalInputSection, FCComboBox, \
+    FCButton, FCLabel
 from camlib import grace
 
 from copy import deepcopy
@@ -602,24 +603,21 @@ class Panelize(AppTool):
                         panel_type, self.outname, job_init_geometry, plot=True, autoselected=True)
 
         if self.constrain_flag is False:
-            self.app.inform.emit('[success] %s' % _("Panel done..."))
+            self.app.inform.emit('[success] %s' % _("Done."))
         else:
             self.constrain_flag = False
             self.app.inform.emit(_("{text} Too big for the constrain area. "
                                    "Final panel has {col} columns and {row} rows").format(
                 text='[WARNING] ', col=columns, row=rows))
 
-        proc = self.app.proc_container.new(_("Working..."))
-
         def job_thread(app_obj):
-            try:
-                panelize_worker()
-                app_obj.inform.emit('[success] %s' % _("Panel created successfully."))
-            except Exception as ee:
-                proc.done()
-                log.debug(str(ee))
-                return
-            proc.done()
+            with self.app.proc_container.new(_("Working ...")):
+                try:
+                    panelize_worker()
+                    app_obj.inform.emit('[success] %s' % _("Panel created successfully."))
+                except Exception as ee:
+                    log.debug(str(ee))
+                    return
 
         self.app.collection.promise(self.outname)
         self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
@@ -639,7 +637,7 @@ class PanelizeUI:
         self.layout = layout
 
         # ## Title
-        title_label = QtWidgets.QLabel("%s" % self.toolName)
+        title_label = FCLabel("%s" % self.toolName)
         title_label.setStyleSheet("""
                                 QLabel
                                 {
@@ -649,7 +647,7 @@ class PanelizeUI:
                                 """)
         self.layout.addWidget(title_label)
 
-        self.object_label = QtWidgets.QLabel('<b>%s:</b>' % _("Source Object"))
+        self.object_label = FCLabel('<b>%s:</b>' % _("Source Object"))
         self.object_label.setToolTip(
             _("Specify the type of object to be panelized\n"
               "It can be of type: Gerber, Excellon or Geometry.\n"
@@ -673,7 +671,7 @@ class PanelizeUI:
         self.type_obj_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/drill16.png"))
         self.type_obj_combo.setItemIcon(2, QtGui.QIcon(self.app.resource_location + "/geometry16.png"))
 
-        self.type_object_label = QtWidgets.QLabel('%s:' % _("Object Type"))
+        self.type_object_label = FCLabel('%s:' % _("Object Type"))
 
         form_layout_0.addRow(self.type_object_label, self.type_obj_combo)
 
@@ -696,7 +694,7 @@ class PanelizeUI:
         # Type of box Panel object
         self.reference_radio = RadioSet([{'label': _('Object'), 'value': 'object'},
                                          {'label': _('Bounding Box'), 'value': 'bbox'}])
-        self.box_label = QtWidgets.QLabel("<b>%s:</b>" % _("Penelization Reference"))
+        self.box_label = FCLabel("<b>%s:</b>" % _("Penelization Reference"))
         self.box_label.setToolTip(
             _("Choose the reference for panelization:\n"
               "- Object = the bounding box of a different object\n"
@@ -719,7 +717,7 @@ class PanelizeUI:
         self.type_box_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png"))
         self.type_box_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/geometry16.png"))
 
-        self.type_box_combo_label = QtWidgets.QLabel('%s:' % _("Box Type"))
+        self.type_box_combo_label = FCLabel('%s:' % _("Box Type"))
         self.type_box_combo_label.setToolTip(
             _("Specify the type of object to be used as an container for\n"
               "panelization. It can be: Gerber or Geometry type.\n"
@@ -745,7 +743,7 @@ class PanelizeUI:
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         form_layout.addRow(separator_line)
 
-        panel_data_label = QtWidgets.QLabel("<b>%s:</b>" % _("Panel Data"))
+        panel_data_label = FCLabel("<b>%s:</b>" % _("Panel Data"))
         panel_data_label.setToolTip(
             _("This informations will shape the resulting panel.\n"
               "The number of rows and columns will set how many\n"
@@ -761,7 +759,7 @@ class PanelizeUI:
         self.spacing_columns.set_range(0, 9999)
         self.spacing_columns.set_precision(4)
 
-        self.spacing_columns_label = QtWidgets.QLabel('%s:' % _("Spacing cols"))
+        self.spacing_columns_label = FCLabel('%s:' % _("Spacing cols"))
         self.spacing_columns_label.setToolTip(
             _("Spacing between columns of the desired panel.\n"
               "In current units.")
@@ -773,7 +771,7 @@ class PanelizeUI:
         self.spacing_rows.set_range(0, 9999)
         self.spacing_rows.set_precision(4)
 
-        self.spacing_rows_label = QtWidgets.QLabel('%s:' % _("Spacing rows"))
+        self.spacing_rows_label = FCLabel('%s:' % _("Spacing rows"))
         self.spacing_rows_label.setToolTip(
             _("Spacing between rows of the desired panel.\n"
               "In current units.")
@@ -784,7 +782,7 @@ class PanelizeUI:
         self.columns = FCSpinner(callback=self.confirmation_message_int)
         self.columns.set_range(0, 9999)
 
-        self.columns_label = QtWidgets.QLabel('%s:' % _("Columns"))
+        self.columns_label = FCLabel('%s:' % _("Columns"))
         self.columns_label.setToolTip(
             _("Number of columns of the desired panel")
         )
@@ -794,7 +792,7 @@ class PanelizeUI:
         self.rows = FCSpinner(callback=self.confirmation_message_int)
         self.rows.set_range(0, 9999)
 
-        self.rows_label = QtWidgets.QLabel('%s:' % _("Rows"))
+        self.rows_label = FCLabel('%s:' % _("Rows"))
         self.rows_label.setToolTip(
             _("Number of rows of the desired panel")
         )
@@ -808,11 +806,11 @@ class PanelizeUI:
         # Type of resulting Panel object
         self.panel_type_radio = RadioSet([{'label': _('Gerber'), 'value': 'gerber'},
                                           {'label': _('Geo'), 'value': 'geometry'}])
-        self.panel_type_label = QtWidgets.QLabel("<b>%s:</b>" % _("Panel Type"))
+        self.panel_type_label = FCLabel("<b>%s:</b>" % _("Panel Type"))
         self.panel_type_label.setToolTip(
             _("Choose the type of object for the panel object:\n"
-              "- Geometry\n"
-              "- Gerber")
+              "- Gerber\n"
+              "- Geometry")
         )
         form_layout.addRow(self.panel_type_label)
         form_layout.addRow(self.panel_type_radio)
@@ -842,7 +840,7 @@ class PanelizeUI:
         self.x_width_entry.set_precision(4)
         self.x_width_entry.set_range(0, 9999)
 
-        self.x_width_lbl = QtWidgets.QLabel('%s:' % _("Width (DX)"))
+        self.x_width_lbl = FCLabel('%s:' % _("Width (DX)"))
         self.x_width_lbl.setToolTip(
             _("The width (DX) within which the panel must fit.\n"
               "In current units.")
@@ -853,7 +851,7 @@ class PanelizeUI:
         self.y_height_entry.set_range(0, 9999)
         self.y_height_entry.set_precision(4)
 
-        self.y_height_lbl = QtWidgets.QLabel('%s:' % _("Height (DY)"))
+        self.y_height_lbl = FCLabel('%s:' % _("Height (DY)"))
         self.y_height_lbl.setToolTip(
             _("The height (DY)within which the panel must fit.\n"
               "In current units.")
@@ -869,7 +867,8 @@ class PanelizeUI:
         form_layout.addRow(separator_line)
 
         # Buttons
-        self.panelize_object_button = QtWidgets.QPushButton(_("Panelize Object"))
+        self.panelize_object_button = FCButton(_("Panelize Object"))
+        self.panelize_object_button.setIcon(QtGui.QIcon(self.app.resource_location + '/panelize16.png'))
         self.panelize_object_button.setToolTip(
             _("Panelize the specified object around the specified box.\n"
               "In other words it creates multiple copies of the source object,\n"
@@ -886,7 +885,7 @@ class PanelizeUI:
         self.layout.addStretch()
 
         # ## Reset Tool
-        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
+        self.reset_button = FCButton(_("Reset Tool"))
         self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png'))
         self.reset_button.setToolTip(
             _("Will reset the tool parameters.")

+ 5 - 5
appTools/ToolPcbWizard.py

@@ -333,7 +333,7 @@ class PcbWizard(AppTool):
 
         if excellon_fileobj is not None and excellon_fileobj != '':
             if self.process_finished:
-                with self.app.proc_container.new(_("Importing Excellon.")):
+                with self.app.proc_container.new('%s ...' % _("Importing")):
 
                     # Object name
                     name = self.outname
@@ -412,7 +412,7 @@ class WizardUI:
         self.tools_table.setVisible(False)
 
         self.layout.addWidget(FCLabel(""))
-        self.layout.addWidget(FCLabel("<b>%s:</b>" % _("Excellon format")))
+        self.layout.addWidget(FCLabel("<b>%s:</b>" % _("Excellon Format")))
         # Form Layout
         form_layout1 = QtWidgets.QFormLayout()
         self.layout.addLayout(form_layout1)
@@ -450,8 +450,8 @@ class WizardUI:
         form_layout1.addRow(self.zeros_label, self.zeros_radio)
 
         # Units type
-        self.units_radio = RadioSet([{'label': _('INCH'), 'value': 'INCH'},
-                                     {'label': _('MM'), 'value': 'METRIC'}])
+        self.units_radio = RadioSet([{'label': _('Inch'), 'value': 'INCH'},
+                                     {'label': _('mm'), 'value': 'METRIC'}])
         self.units_label = FCLabel("<b>%s:</b>" % _('Units'))
         self.units_label.setToolTip(
             _("The type of units that the coordinates and tool\n"
@@ -463,7 +463,7 @@ class WizardUI:
 
         self.import_button = QtWidgets.QPushButton(_("Import Excellon"))
         self.import_button.setToolTip(
-            _("Import in FlatCAM an Excellon file\n"
+            _("Import an Excellon file\n"
               "that store it's information's in 2 files.\n"
               "One usually has .DRL extension while\n"
               "the other has .INF extension.")

+ 2 - 2
appTools/ToolProperties.py

@@ -109,7 +109,7 @@ class Properties(AppTool):
     def properties(self):
         obj_list = self.app.collection.get_selected()
         if not obj_list:
-            self.app.inform.emit('[ERROR_NOTCL] %s' % _("No object selected."))
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _("No object is selected."))
             self.app.ui.notebook.setTabText(2, _("Tools"))
             self.properties_frame.hide()
             self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
@@ -192,7 +192,7 @@ class Properties(AppTool):
         self.treeWidget.addChild(obj_name, [obj.options['name']])
 
         def job_thread(obj_prop):
-            self.app.proc_container.new(_("Calculating dimensions ... Please wait."))
+            self.app.proc_container.new(_("Working ..."))
 
             length = 0.0
             width = 0.0

File diff suppressed because it is too large
+ 545 - 256
appTools/ToolPunchGerber.py


+ 63 - 56
appTools/ToolQRCode.py

@@ -163,47 +163,47 @@ class QRCode(AppTool):
         self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_release)
         self.kr = self.app.plotcanvas.graph_event_connect('key_release', self.on_key_release)
 
-        self.proc = self.app.proc_container.new('%s...' % _("Generating QRCode geometry"))
-
         def job_thread_qr(app_obj):
-            error_code = {
-                'L': qrcode.constants.ERROR_CORRECT_L,
-                'M': qrcode.constants.ERROR_CORRECT_M,
-                'Q': qrcode.constants.ERROR_CORRECT_Q,
-                'H': qrcode.constants.ERROR_CORRECT_H
-            }[self.ui.error_radio.get_value()]
-
-            qr = qrcode.QRCode(
-                version=self.ui.version_entry.get_value(),
-                error_correction=error_code,
-                box_size=self.ui.bsize_entry.get_value(),
-                border=self.ui.border_size_entry.get_value(),
-                image_factory=qrcode.image.svg.SvgFragmentImage
-            )
-            qr.add_data(text_data)
-            qr.make()
-
-            svg_file = BytesIO()
-            img = qr.make_image()
-            img.save(svg_file)
-
-            svg_text = StringIO(svg_file.getvalue().decode('UTF-8'))
-            svg_geometry = self.convert_svg_to_geo(svg_text, units=self.units)
-            self.qrcode_geometry = deepcopy(svg_geometry)
-
-            svg_geometry = unary_union(svg_geometry).buffer(0.0000001).buffer(-0.0000001)
-            self.qrcode_utility_geometry = svg_geometry
-
-            # make a bounding box of the QRCode geometry to help drawing the utility geometry in case it is too
-            # complicated
-            try:
-                a, b, c, d = self.qrcode_utility_geometry.bounds
-                self.box_poly = box(minx=a, miny=b, maxx=c, maxy=d)
-            except Exception as ee:
-                log.debug("QRCode.make() bounds error --> %s" % str(ee))
+            with self.app.proc_container.new('%s' % _("Working ...")) as self.proc:
+
+                error_code = {
+                    'L': qrcode.constants.ERROR_CORRECT_L,
+                    'M': qrcode.constants.ERROR_CORRECT_M,
+                    'Q': qrcode.constants.ERROR_CORRECT_Q,
+                    'H': qrcode.constants.ERROR_CORRECT_H
+                }[self.ui.error_radio.get_value()]
+
+                qr = qrcode.QRCode(
+                    version=self.ui.version_entry.get_value(),
+                    error_correction=error_code,
+                    box_size=self.ui.bsize_entry.get_value(),
+                    border=self.ui.border_size_entry.get_value(),
+                    image_factory=qrcode.image.svg.SvgFragmentImage
+                )
+                qr.add_data(text_data)
+                qr.make()
+
+                svg_file = BytesIO()
+                img = qr.make_image()
+                img.save(svg_file)
+
+                svg_text = StringIO(svg_file.getvalue().decode('UTF-8'))
+                svg_geometry = self.convert_svg_to_geo(svg_text, units=self.units)
+                self.qrcode_geometry = deepcopy(svg_geometry)
+
+                svg_geometry = unary_union(svg_geometry).buffer(0.0000001).buffer(-0.0000001)
+                self.qrcode_utility_geometry = svg_geometry
+
+                # make a bounding box of the QRCode geometry to help drawing the utility geometry in case it is too
+                # complicated
+                try:
+                    a, b, c, d = self.qrcode_utility_geometry.bounds
+                    self.box_poly = box(minx=a, miny=b, maxx=c, maxy=d)
+                except Exception as ee:
+                    log.debug("QRCode.make() bounds error --> %s" % str(ee))
 
-            app_obj.call_source = 'qrcode_tool'
-            app_obj.inform.emit(_("Click on the Destination point ..."))
+                app_obj.call_source = 'qrcode_tool'
+                app_obj.inform.emit(_("Click on the DESTINATION point ..."))
 
         self.app.worker_task.emit({'fcn': job_thread_qr, 'params': [self.app]})
 
@@ -270,9 +270,11 @@ class QRCode(AppTool):
 
         # don't know if the condition is required since I already made sure above that the new_apid is a new one
         if new_apid not in self.grb_object.apertures:
-            self.grb_object.apertures[new_apid] = {}
-            self.grb_object.apertures[new_apid]['geometry'] = []
-            self.grb_object.apertures[new_apid]['type'] = 'R'
+            self.grb_object.apertures[new_apid] = {
+                'type': 'R',
+                'geometry': []
+            }
+
             # TODO: HACK
             # I've artificially added 1% to the height and width because otherwise after loading the
             # exported file, it will not be correctly reconstructed (it will be made from multiple shapes instead of
@@ -282,15 +284,15 @@ class QRCode(AppTool):
             self.grb_object.apertures[new_apid]['size'] = deepcopy(math.sqrt(box_size ** 2 + box_size ** 2))
 
         if '0' not in self.grb_object.apertures:
-            self.grb_object.apertures['0'] = {}
-            self.grb_object.apertures['0']['geometry'] = []
-            self.grb_object.apertures['0']['type'] = 'REG'
-            self.grb_object.apertures['0']['size'] = 0.0
+            self.grb_object.apertures['0'] = {
+                'type': 'REG',
+                'size': 0.0,
+                'geometry': []
+            }
 
         # in case that the QRCode geometry is dropped onto a copper region (found in the '0' aperture)
         # make sure that I place a cutout there
-        zero_elem = {}
-        zero_elem['clear'] = offset_mask_geo
+        zero_elem = {'clear': offset_mask_geo}
         self.grb_object.apertures['0']['geometry'].append(deepcopy(zero_elem))
 
         try:
@@ -304,13 +306,13 @@ class QRCode(AppTool):
 
         try:
             for geo in self.qrcode_geometry:
-                geo_elem = {}
-                geo_elem['solid'] = translate(geo, xoff=pos[0], yoff=pos[1])
-                geo_elem['follow'] = translate(geo.centroid, xoff=pos[0], yoff=pos[1])
+                geo_elem = {
+                    'solid': translate(geo, xoff=pos[0], yoff=pos[1]),
+                    'follow': translate(geo.centroid, xoff=pos[0], yoff=pos[1])
+                }
                 self.grb_object.apertures[new_apid]['geometry'].append(deepcopy(geo_elem))
         except TypeError:
-            geo_elem = {}
-            geo_elem['solid'] = self.qrcode_geometry
+            geo_elem = {'solid': self.qrcode_geometry}
             self.grb_object.apertures[new_apid]['geometry'].append(deepcopy(geo_elem))
 
         # update the source file with the new geometry:
@@ -461,7 +463,7 @@ class QRCode(AppTool):
 
     def replot(self, obj):
         def worker_task():
-            with self.app.proc_container.new('%s...' % _("Plotting")):
+            with self.app.proc_container.new('%s ...' % _("Plotting")):
                 obj.plot()
 
         self.app.worker_task.emit({'fcn': worker_task, 'params': []})
@@ -519,7 +521,9 @@ class QRCode(AppTool):
                 directory=self.app.get_last_save_folder() + '/' + str(name) + '_png',
                 ext_filter=_filter)
         except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export PNG"), ext_filter=_filter)
+            filename, _f = FCFileSaveDialog.get_saved_filename(
+                caption=_("Export PNG"),
+                ext_filter=_filter)
 
         filename = str(filename)
 
@@ -566,7 +570,9 @@ class QRCode(AppTool):
                 directory=self.app.get_last_save_folder() + '/' + str(name) + '_svg',
                 ext_filter=_filter)
         except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export SVG"), ext_filter=_filter)
+            filename, _f = FCFileSaveDialog.get_saved_filename(
+                caption=_("Export SVG"),
+                ext_filter=_filter)
 
         filename = str(filename)
 
@@ -888,6 +894,7 @@ class QRcodeUI:
 
         # ## Insert QRCode
         self.qrcode_button = QtWidgets.QPushButton(_("Insert QRCode"))
+        self.qrcode_button.setIcon(QtGui.QIcon(self.app.resource_location + '/qrcode32.png'))
         self.qrcode_button.setToolTip(
             _("Create the QRCode object.")
         )

+ 63 - 56
appTools/ToolRulesCheck.py

@@ -631,16 +631,18 @@ class RulesCheck(AppTool):
                 copper_list = []
                 copper_name_1 = self.ui.copper_t_object.currentText()
                 if copper_name_1 != '' and self.ui.copper_t_cb.get_value():
-                    elem_dict = {}
-                    elem_dict['name'] = deepcopy(copper_name_1)
-                    elem_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_name_1).apertures)
+                    elem_dict = {
+                        'name': deepcopy(copper_name_1),
+                        'apertures': deepcopy(app_obj.collection.get_by_name(copper_name_1).apertures)
+                    }
                     copper_list.append(elem_dict)
 
                 copper_name_2 = self.ui.copper_b_object.currentText()
                 if copper_name_2 != '' and self.ui.copper_b_cb.get_value():
-                    elem_dict = {}
-                    elem_dict['name'] = deepcopy(copper_name_2)
-                    elem_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_name_2).apertures)
+                    elem_dict = {
+                        'name': deepcopy(copper_name_2),
+                        'apertures': deepcopy(app_obj.collection.get_by_name(copper_name_2).apertures)
+                    }
                     copper_list.append(elem_dict)
 
                 trace_size = float(self.ui.trace_size_entry.get_value())
@@ -664,7 +666,7 @@ class RulesCheck(AppTool):
 
                     if copper_t_obj != '':
                         copper_t_dict['name'] = deepcopy(copper_t_obj)
-                        copper_t_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_t_obj).apertures)
+                        copper_t_dict['apertures'] = deepcopy(app_obj.collection.get_by_name(copper_t_obj).apertures)
 
                         self.results.append(self.pool.apply_async(self.check_inside_gerber_clearance,
                                                                   args=(copper_t_dict,
@@ -675,7 +677,7 @@ class RulesCheck(AppTool):
                     copper_b_dict = {}
                     if copper_b_obj != '':
                         copper_b_dict['name'] = deepcopy(copper_b_obj)
-                        copper_b_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_b_obj).apertures)
+                        copper_b_dict['apertures'] = deepcopy(app_obj.collection.get_by_name(copper_b_obj).apertures)
 
                         self.results.append(self.pool.apply_async(self.check_inside_gerber_clearance,
                                                                   args=(copper_b_dict,
@@ -683,7 +685,7 @@ class RulesCheck(AppTool):
                                                                         _("BOTTOM -> Copper to Copper clearance"))))
 
                 if self.ui.copper_t_cb.get_value() is False and self.ui.copper_b_cb.get_value() is False:
-                    self.app.inform.emit('[ERROR_NOTCL] %s. %s' % (
+                    app_obj.inform.emit('[ERROR_NOTCL] %s. %s' % (
                         _("Copper to Copper clearance"),
                         _("At least one Gerber object has to be selected for this rule but none is selected.")))
                     return
@@ -697,29 +699,29 @@ class RulesCheck(AppTool):
                 copper_top = self.ui.copper_t_object.currentText()
                 if copper_top != '' and self.ui.copper_t_cb.get_value():
                     top_dict['name'] = deepcopy(copper_top)
-                    top_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_top).apertures)
+                    top_dict['apertures'] = deepcopy(app_obj.collection.get_by_name(copper_top).apertures)
 
                 copper_bottom = self.ui.copper_b_object.currentText()
                 if copper_bottom != '' and self.ui.copper_b_cb.get_value():
                     bottom_dict['name'] = deepcopy(copper_bottom)
-                    bottom_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_bottom).apertures)
+                    bottom_dict['apertures'] = deepcopy(app_obj.collection.get_by_name(copper_bottom).apertures)
 
                 copper_outline = self.ui.outline_object.currentText()
                 if copper_outline != '' and self.ui.out_cb.get_value():
                     outline_dict['name'] = deepcopy(copper_outline)
-                    outline_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_outline).apertures)
+                    outline_dict['apertures'] = deepcopy(app_obj.collection.get_by_name(copper_outline).apertures)
 
                 try:
                     copper_outline_clearance = float(self.ui.clearance_copper2ol_entry.get_value())
                 except Exception as e:
                     log.debug("RulesCheck.execute.worker_job() --> %s" % str(e))
-                    self.app.inform.emit('[ERROR_NOTCL] %s. %s' % (
+                    app_obj.inform.emit('[ERROR_NOTCL] %s. %s' % (
                         _("Copper to Outline clearance"),
                         _("Value is not valid.")))
                     return
 
                 if not top_dict and not bottom_dict or not outline_dict:
-                    self.app.inform.emit('[ERROR_NOTCL] %s. %s' % (
+                    app_obj.inform.emit('[ERROR_NOTCL] %s. %s' % (
                         _("Copper to Outline clearance"),
                         _("One of the copper Gerber objects or the Outline Gerber object is not valid.")))
                     return
@@ -732,7 +734,7 @@ class RulesCheck(AppTool):
                 if outline_dict:
                     objs.append(outline_dict)
                 else:
-                    self.app.inform.emit('[ERROR_NOTCL] %s. %s' % (
+                    app_obj.inform.emit('[ERROR_NOTCL] %s. %s' % (
                         _("Copper to Outline clearance"),
                         _("Outline Gerber object presence is mandatory for this rule but it is not selected.")))
                     return
@@ -750,7 +752,7 @@ class RulesCheck(AppTool):
                     silk_silk_clearance = float(self.ui.clearance_silk2silk_entry.get_value())
                 except Exception as e:
                     log.debug("RulesCheck.execute.worker_job() --> %s" % str(e))
-                    self.app.inform.emit('[ERROR_NOTCL] %s. %s' % (
+                    app_obj.inform.emit('[ERROR_NOTCL] %s. %s' % (
                         _("Silk to Silk clearance"),
                         _("Value is not valid.")))
                     return
@@ -759,7 +761,7 @@ class RulesCheck(AppTool):
                     silk_obj = self.ui.ss_t_object.currentText()
                     if silk_obj != '':
                         silk_dict['name'] = deepcopy(silk_obj)
-                        silk_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_obj).apertures)
+                        silk_dict['apertures'] = deepcopy(app_obj.collection.get_by_name(silk_obj).apertures)
 
                         self.results.append(self.pool.apply_async(self.check_inside_gerber_clearance,
                                                                   args=(silk_dict,
@@ -769,7 +771,7 @@ class RulesCheck(AppTool):
                     silk_obj = self.ui.ss_b_object.currentText()
                     if silk_obj != '':
                         silk_dict['name'] = deepcopy(silk_obj)
-                        silk_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_obj).apertures)
+                        silk_dict['apertures'] = deepcopy(app_obj.collection.get_by_name(silk_obj).apertures)
 
                         self.results.append(self.pool.apply_async(self.check_inside_gerber_clearance,
                                                                   args=(silk_dict,
@@ -777,7 +779,7 @@ class RulesCheck(AppTool):
                                                                         _("BOTTOM -> Silk to Silk clearance"))))
 
                 if self.ui.ss_t_cb.get_value() is False and self.ui.ss_b_cb.get_value() is False:
-                    self.app.inform.emit('[ERROR_NOTCL] %s. %s' % (
+                    app_obj.inform.emit('[ERROR_NOTCL] %s. %s' % (
                         _("Silk to Silk clearance"),
                         _("At least one Gerber object has to be selected for this rule but none is selected.")))
                     return
@@ -797,38 +799,38 @@ class RulesCheck(AppTool):
                 silk_top = self.ui.ss_t_object.currentText()
                 if silk_top != '' and self.ui.ss_t_cb.get_value():
                     silk_t_dict['name'] = deepcopy(silk_top)
-                    silk_t_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_top).apertures)
+                    silk_t_dict['apertures'] = deepcopy(app_obj.collection.get_by_name(silk_top).apertures)
                     top_ss = True
 
                 silk_bottom = self.ui.ss_b_object.currentText()
                 if silk_bottom != '' and self.ui.ss_b_cb.get_value():
                     silk_b_dict['name'] = deepcopy(silk_bottom)
-                    silk_b_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_bottom).apertures)
+                    silk_b_dict['apertures'] = deepcopy(app_obj.collection.get_by_name(silk_bottom).apertures)
                     bottom_ss = True
 
                 sm_top = self.ui.sm_t_object.currentText()
                 if sm_top != '' and self.ui.sm_t_cb.get_value():
                     sm_t_dict['name'] = deepcopy(sm_top)
-                    sm_t_dict['apertures'] = deepcopy(self.app.collection.get_by_name(sm_top).apertures)
+                    sm_t_dict['apertures'] = deepcopy(app_obj.collection.get_by_name(sm_top).apertures)
                     top_sm = True
 
                 sm_bottom = self.ui.sm_b_object.currentText()
                 if sm_bottom != '' and self.ui.sm_b_cb.get_value():
                     sm_b_dict['name'] = deepcopy(sm_bottom)
-                    sm_b_dict['apertures'] = deepcopy(self.app.collection.get_by_name(sm_bottom).apertures)
+                    sm_b_dict['apertures'] = deepcopy(app_obj.collection.get_by_name(sm_bottom).apertures)
                     bottom_sm = True
 
                 try:
                     silk_sm_clearance = float(self.ui.clearance_silk2sm_entry.get_value())
                 except Exception as e:
                     log.debug("RulesCheck.execute.worker_job() --> %s" % str(e))
-                    self.app.inform.emit('[ERROR_NOTCL] %s. %s' % (
+                    app_obj.inform.emit('[ERROR_NOTCL] %s. %s' % (
                         _("Silk to Solder Mask Clearance"),
                         _("Value is not valid.")))
                     return
 
                 if (not silk_t_dict and not silk_b_dict) or (not sm_t_dict and not sm_b_dict):
-                    self.app.inform.emit('[ERROR_NOTCL] %s. %s' % (
+                    app_obj.inform.emit('[ERROR_NOTCL] %s. %s' % (
                         _("Silk to Solder Mask Clearance"),
                         _("One or more of the Gerber objects is not valid.")))
                     return
@@ -846,7 +848,7 @@ class RulesCheck(AppTool):
                                                                     silk_sm_clearance,
                                                                     _("BOTTOM -> Silk to Solder Mask Clearance"))))
                 else:
-                    self.app.inform.emit('[ERROR_NOTCL] %s. %s' % (
+                    app_obj.inform.emit('[ERROR_NOTCL] %s. %s' % (
                         _("Silk to Solder Mask Clearance"),
                         _("Both Silk and Solder Mask Gerber objects has to be either both Top or both Bottom.")))
                     return
@@ -860,29 +862,29 @@ class RulesCheck(AppTool):
                 silk_top = self.ui.ss_t_object.currentText()
                 if silk_top != '' and self.ui.ss_t_cb.get_value():
                     top_dict['name'] = deepcopy(silk_top)
-                    top_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_top).apertures)
+                    top_dict['apertures'] = deepcopy(app_obj.collection.get_by_name(silk_top).apertures)
 
                 silk_bottom = self.ui.ss_b_object.currentText()
                 if silk_bottom != '' and self.ui.ss_b_cb.get_value():
                     bottom_dict['name'] = deepcopy(silk_bottom)
-                    bottom_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_bottom).apertures)
+                    bottom_dict['apertures'] = deepcopy(app_obj.collection.get_by_name(silk_bottom).apertures)
 
                 copper_outline = self.ui.outline_object.currentText()
                 if copper_outline != '' and self.ui.out_cb.get_value():
                     outline_dict['name'] = deepcopy(copper_outline)
-                    outline_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_outline).apertures)
+                    outline_dict['apertures'] = deepcopy(app_obj.collection.get_by_name(copper_outline).apertures)
 
                 try:
                     copper_outline_clearance = float(self.ui.clearance_copper2ol_entry.get_value())
                 except Exception as e:
                     log.debug("RulesCheck.execute.worker_job() --> %s" % str(e))
-                    self.app.inform.emit('[ERROR_NOTCL] %s. %s' % (
+                    app_obj.inform.emit('[ERROR_NOTCL] %s. %s' % (
                         _("Silk to Outline Clearance"),
                         _("Value is not valid.")))
                     return
 
                 if not top_dict and not bottom_dict or not outline_dict:
-                    self.app.inform.emit('[ERROR_NOTCL] %s. %s' % (
+                    app_obj.inform.emit('[ERROR_NOTCL] %s. %s' % (
                         _("Silk to Outline Clearance"),
                         _("One of the Silk Gerber objects or the Outline Gerber object is not valid.")))
                     return
@@ -896,7 +898,7 @@ class RulesCheck(AppTool):
                 if outline_dict:
                     objs.append(outline_dict)
                 else:
-                    self.app.inform.emit('[ERROR_NOTCL] %s. %s' % (
+                    app_obj.inform.emit('[ERROR_NOTCL] %s. %s' % (
                         _("Silk to Outline Clearance"),
                         _("Outline Gerber object presence is mandatory for this rule but it is not selected.")))
                     return
@@ -914,7 +916,7 @@ class RulesCheck(AppTool):
                     sm_sm_clearance = float(self.ui.clearance_sm2sm_entry.get_value())
                 except Exception as e:
                     log.debug("RulesCheck.execute.worker_job() --> %s" % str(e))
-                    self.app.inform.emit('[ERROR_NOTCL] %s. %s' % (
+                    app_obj.inform.emit('[ERROR_NOTCL] %s. %s' % (
                         _("Minimum Solder Mask Sliver"),
                         _("Value is not valid.")))
                     return
@@ -923,7 +925,7 @@ class RulesCheck(AppTool):
                     solder_obj = self.ui.sm_t_object.currentText()
                     if solder_obj != '':
                         sm_dict['name'] = deepcopy(solder_obj)
-                        sm_dict['apertures'] = deepcopy(self.app.collection.get_by_name(solder_obj).apertures)
+                        sm_dict['apertures'] = deepcopy(app_obj.collection.get_by_name(solder_obj).apertures)
 
                         self.results.append(self.pool.apply_async(self.check_inside_gerber_clearance,
                                                                   args=(sm_dict,
@@ -933,7 +935,7 @@ class RulesCheck(AppTool):
                     solder_obj = self.ui.sm_b_object.currentText()
                     if solder_obj != '':
                         sm_dict['name'] = deepcopy(solder_obj)
-                        sm_dict['apertures'] = deepcopy(self.app.collection.get_by_name(solder_obj).apertures)
+                        sm_dict['apertures'] = deepcopy(app_obj.collection.get_by_name(solder_obj).apertures)
 
                         self.results.append(self.pool.apply_async(self.check_inside_gerber_clearance,
                                                                   args=(sm_dict,
@@ -941,7 +943,7 @@ class RulesCheck(AppTool):
                                                                         _("BOTTOM -> Minimum Solder Mask Sliver"))))
 
                 if self.ui.sm_t_cb.get_value() is False and self.ui.sm_b_cb.get_value() is False:
-                    self.app.inform.emit('[ERROR_NOTCL] %s. %s' % (
+                    app_obj.inform.emit('[ERROR_NOTCL] %s. %s' % (
                         _("Minimum Solder Mask Sliver"),
                         _("At least one Gerber object has to be selected for this rule but none is selected.")))
                     return
@@ -956,36 +958,36 @@ class RulesCheck(AppTool):
                 copper_top = self.ui.copper_t_object.currentText()
                 if copper_top != '' and self.ui.copper_t_cb.get_value():
                     top_dict['name'] = deepcopy(copper_top)
-                    top_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_top).apertures)
+                    top_dict['apertures'] = deepcopy(app_obj.collection.get_by_name(copper_top).apertures)
 
                 copper_bottom = self.ui.copper_b_object.currentText()
                 if copper_bottom != '' and self.ui.copper_b_cb.get_value():
                     bottom_dict['name'] = deepcopy(copper_bottom)
-                    bottom_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_bottom).apertures)
+                    bottom_dict['apertures'] = deepcopy(app_obj.collection.get_by_name(copper_bottom).apertures)
 
                 excellon_1 = self.ui.e1_object.currentText()
                 if excellon_1 != '' and self.ui.e1_cb.get_value():
                     exc_1_dict['name'] = deepcopy(excellon_1)
                     exc_1_dict['tools'] = deepcopy(
-                        self.app.collection.get_by_name(excellon_1).tools)
+                        app_obj.collection.get_by_name(excellon_1).tools)
 
                 excellon_2 = self.ui.e2_object.currentText()
                 if excellon_2 != '' and self.ui.e2_cb.get_value():
                     exc_2_dict['name'] = deepcopy(excellon_2)
                     exc_2_dict['tools'] = deepcopy(
-                        self.app.collection.get_by_name(excellon_2).tools)
+                        app_obj.collection.get_by_name(excellon_2).tools)
 
                 try:
                     ring_val = float(self.ui.ring_integrity_entry.get_value())
                 except Exception as e:
                     log.debug("RulesCheck.execute.worker_job() --> %s" % str(e))
-                    self.app.inform.emit('[ERROR_NOTCL] %s. %s' % (
+                    app_obj.inform.emit('[ERROR_NOTCL] %s. %s' % (
                         _("Minimum Annular Ring"),
                         _("Value is not valid.")))
                     return
 
                 if (not top_dict and not bottom_dict) or (not exc_1_dict and not exc_2_dict):
-                    self.app.inform.emit('[ERROR_NOTCL] %s. %s' % (
+                    app_obj.inform.emit('[ERROR_NOTCL] %s. %s' % (
                         _("Minimum Annular Ring"),
                         _("One of the Copper Gerber objects or the Excellon objects is not valid.")))
                     return
@@ -1001,7 +1003,7 @@ class RulesCheck(AppTool):
                 elif exc_2_dict:
                     objs.append(exc_2_dict)
                 else:
-                    self.app.inform.emit('[ERROR_NOTCL] %s. %s' % (
+                    app_obj.inform.emit('[ERROR_NOTCL] %s. %s' % (
                         _("Minimum Annular Ring"),
                         _("Excellon object presence is mandatory for this rule but none is selected.")))
                     return
@@ -1016,16 +1018,18 @@ class RulesCheck(AppTool):
                 exc_list = []
                 exc_name_1 = self.ui.e1_object.currentText()
                 if exc_name_1 != '' and self.ui.e1_cb.get_value():
-                    elem_dict = {}
-                    elem_dict['name'] = deepcopy(exc_name_1)
-                    elem_dict['tools'] = deepcopy(self.app.collection.get_by_name(exc_name_1).tools)
+                    elem_dict = {
+                        'name': deepcopy(exc_name_1),
+                        'tools': deepcopy(app_obj.collection.get_by_name(exc_name_1).tools)
+                    }
                     exc_list.append(elem_dict)
 
                 exc_name_2 = self.ui.e2_object.currentText()
                 if exc_name_2 != '' and self.ui.e2_cb.get_value():
-                    elem_dict = {}
-                    elem_dict['name'] = deepcopy(exc_name_2)
-                    elem_dict['tools'] = deepcopy(self.app.collection.get_by_name(exc_name_2).tools)
+                    elem_dict = {
+                        'name': deepcopy(exc_name_2),
+                        'tools': deepcopy(app_obj.collection.get_by_name(exc_name_2).tools)
+                    }
                     exc_list.append(elem_dict)
 
                 hole_clearance = float(self.ui.clearance_d2d_entry.get_value())
@@ -1036,16 +1040,18 @@ class RulesCheck(AppTool):
                 exc_list = []
                 exc_name_1 = self.ui.e1_object.currentText()
                 if exc_name_1 != '' and self.ui.e1_cb.get_value():
-                    elem_dict = {}
-                    elem_dict['name'] = deepcopy(exc_name_1)
-                    elem_dict['tools'] = deepcopy(self.app.collection.get_by_name(exc_name_1).tools)
+                    elem_dict = {
+                        'name': deepcopy(exc_name_1),
+                        'tools': deepcopy(app_obj.collection.get_by_name(exc_name_1).tools)
+                    }
                     exc_list.append(elem_dict)
 
                 exc_name_2 = self.ui.e2_object.currentText()
                 if exc_name_2 != '' and self.ui.e2_cb.get_value():
-                    elem_dict = {}
-                    elem_dict['name'] = deepcopy(exc_name_2)
-                    elem_dict['tools'] = deepcopy(self.app.collection.get_by_name(exc_name_2).tools)
+                    elem_dict = {
+                        'name': deepcopy(exc_name_2),
+                        'tools': deepcopy(app_obj.collection.get_by_name(exc_name_2).tools)
+                    }
                     exc_list.append(elem_dict)
 
                 drill_size = float(self.ui.drill_size_entry.get_value())
@@ -1056,7 +1062,7 @@ class RulesCheck(AppTool):
                 output.append(p.get())
 
             self.tool_finished.emit(output)
-            self.app.proc_container.view.set_idle()
+            app_obj.proc_container.view.set_idle()
 
             log.debug("RuleCheck() finished")
 
@@ -1601,6 +1607,7 @@ class RulesUI:
 
         # hlay_2.addStretch()
         self.run_button = FCButton(_("Run Rules Check"))
+        self.run_button.setIcon(QtGui.QIcon(self.app.resource_location + '/rules32.png'))
         self.run_button.setToolTip(
             _("Panelize the specified object around the specified box.\n"
               "In other words it creates multiple copies of the source object,\n"

+ 3 - 0
appTools/ToolShell.py

@@ -74,6 +74,9 @@ class TermWidget(QWidget):
 
         self._delete_line.clicked.connect(self.on_delete_line_clicked)
 
+    def command_line(self):
+        return self._edit
+
     def on_delete_line_clicked(self):
         self._edit.clear()
 

+ 30 - 28
appTools/ToolSolderPaste.py

@@ -7,8 +7,8 @@
 
 from appTool import AppTool
 from appCommon.Common import LoudDict
-from appGUI.GUIElements import FCComboBox, FCEntry, FCTable, \
-    FCInputDialog, FCDoubleSpinner, FCSpinner, FCFileSaveDialog
+from appGUI.GUIElements import FCComboBox, FCEntry, FCTable, FCDoubleSpinner, FCSpinner, FCFileSaveDialog, \
+    FCInputSpinner
 from app_Main import log
 from camlib import distance
 from appEditors.AppTextEditor import AppTextEditor
@@ -119,9 +119,9 @@ class SolderPaste(AppTool):
         AppTool.install(self, icon, separator, shortcut='Alt+K', **kwargs)
 
     def on_add_tool_by_key(self):
-        tool_add_popup = FCInputDialog(title='%s...' % _("New Tool"),
-                                       text='%s:' % _('Enter a Tool Diameter'),
-                                       min=0.0000, max=99.9999, decimals=4)
+        tool_add_popup = FCInputSpinner(title='%s...' % _("New Tool"),
+                                        text='%s:' % _('Enter a Tool Diameter'),
+                                        min=0.0000, max=100.0000, decimals=self.decimals, step=0.1)
         tool_add_popup.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/letter_t_32.png'))
 
         val, ok = tool_add_popup.get_value()
@@ -300,7 +300,7 @@ class SolderPaste(AppTool):
                 if int(tooluid_key) == tooluid:
                     self.set_form(deepcopy(tooluid_value['data']))
         except Exception as e:
-            log.debug("FlatCAMObj ---> update_ui() " + str(e))
+            log.debug("ToolSolderPaste ---> update_ui() " + str(e))
 
         self.ui_connect()
 
@@ -590,12 +590,12 @@ class SolderPaste(AppTool):
                     self.tooltable_tools.pop(t, None)
 
         except AttributeError:
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Delete failed. Select a Nozzle tool to delete."))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Delete failed. Select a tool to delete."))
             return
         except Exception as e:
             log.debug(str(e))
 
-        self.app.inform.emit('[success] %s' % _("Nozzle tool(s) deleted from Tool Table."))
+        self.app.inform.emit('[success] %s' % _("Tools deleted from Tool Table."))
         self.build_ui()
 
     def on_rmb_combo(self, pos, combo):
@@ -667,7 +667,7 @@ class SolderPaste(AppTool):
         :param use_thread: use thread, True or False
         :return: a Geometry type object
         """
-        proc = self.app.proc_container.new(_("Creating Solder Paste dispensing geometry."))
+        proc = self.app.proc_container.new(_("Working ..."))
         obj = work_object
 
         # Sort tools in descending order
@@ -806,7 +806,8 @@ class SolderPaste(AppTool):
                         if not geo_obj.tools[tooluid_key]['solid_geometry']:
                             a += 1
                     if a == len(geo_obj.tools):
-                        self.app.inform.emit('[ERROR_NOTCL] %s' % _('Cancelled. Empty file, it has no geometry...'))
+                        msg = '[ERROR_NOTCL] %s' % '%s ...' % _('Cancelled. Empty file, it has no geometry')
+                        self.app.inform.emit(msg)
                         return 'fail'
 
                     app_obj.inform.emit('[success] %s...' % _("Solder Paste geometry generated successfully"))
@@ -890,7 +891,7 @@ class SolderPaste(AppTool):
             ymax = obj.options['ymax']
         except Exception as e:
             log.debug("SolderPaste.on_create_gcode() --> %s\n" % str(e))
-            msg = '[ERROR] %s' % _("An internal error has ocurred. See shell.\n")
+            msg = '[ERROR] %s' % _("An internal error has occurred. See shell.\n")
             msg += 'SolderPaste.on_create_gcode() --> %s' % str(e)
             msg += traceback.format_exc()
             self.app.inform.emit(msg)
@@ -956,7 +957,7 @@ class SolderPaste(AppTool):
         if use_thread:
             # To be run in separate thread
             def job_thread(app_obj):
-                with self.app.proc_container.new("Generating CNC Code"):
+                with self.app.proc_container.new('%s' % _("Working ...")):
                     if app_obj.app_obj.new_object("cncjob", name, job_init) != 'fail':
                         app_obj.inform.emit('[success] [success] %s: %s' %
                                             (_("ToolSolderPaste CNCjob created"), name))
@@ -1060,7 +1061,8 @@ class SolderPaste(AppTool):
             )
         except TypeError:
             filename, _f = FCFileSaveDialog.get_saved_filename(
-                caption=_("Export Code ..."), ext_filter=_filter_)
+                caption=_("Export Code ..."),
+                ext_filter=_filter_)
 
         if filename == '':
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export cancelled ..."))
@@ -1176,7 +1178,7 @@ class SolderUI:
               "with solder paste, the app will issue a warning message box.")
         )
         self.tools_table.horizontalHeaderItem(1).setToolTip(
-            _("Nozzle tool Diameter. It's value (in current FlatCAM units)\n"
+            _("Tool Diameter. Its value\n"
               "is the width of the solder paste dispensed."))
 
         # ### Add a new Tool ## ##
@@ -1185,10 +1187,10 @@ class SolderUI:
 
         self.addtool_entry_lbl = QtWidgets.QLabel('<b>%s:</b>' % _('New Nozzle Tool'))
         self.addtool_entry_lbl.setToolTip(
-            _("Diameter for the new Nozzle tool to add in the Tool Table")
+            _("Diameter for the new tool to add in the Tool Table")
         )
         self.addtool_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.addtool_entry.set_range(0.0000001, 9999.9999)
+        self.addtool_entry.set_range(0.0000001, 10000.0000)
         self.addtool_entry.set_precision(self.decimals)
         self.addtool_entry.setSingleStep(0.1)
 
@@ -1209,7 +1211,7 @@ class SolderUI:
         self.deltool_btn = QtWidgets.QPushButton(_('Delete'))
         self.deltool_btn.setToolTip(
             _("Delete a selection of tools in the Tool Table\n"
-              "by first selecting a row(s) in the Tool Table.")
+              "by first selecting a row in the Tool Table.")
         )
 
         grid0.addWidget(self.addtool_btn, 0, 0)
@@ -1248,7 +1250,7 @@ class SolderUI:
 
         # Z dispense start
         self.z_start_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.z_start_entry.set_range(0.0000001, 9999.9999)
+        self.z_start_entry.set_range(0.0000001, 10000.0000)
         self.z_start_entry.set_precision(self.decimals)
         self.z_start_entry.setSingleStep(0.1)
 
@@ -1260,7 +1262,7 @@ class SolderUI:
 
         # Z dispense
         self.z_dispense_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.z_dispense_entry.set_range(0.0000001, 9999.9999)
+        self.z_dispense_entry.set_range(0.0000001, 10000.0000)
         self.z_dispense_entry.set_precision(self.decimals)
         self.z_dispense_entry.setSingleStep(0.1)
 
@@ -1272,7 +1274,7 @@ class SolderUI:
 
         # Z dispense stop
         self.z_stop_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.z_stop_entry.set_range(0.0000001, 9999.9999)
+        self.z_stop_entry.set_range(0.0000001, 10000.0000)
         self.z_stop_entry.set_precision(self.decimals)
         self.z_stop_entry.setSingleStep(0.1)
 
@@ -1284,7 +1286,7 @@ class SolderUI:
 
         # Z travel
         self.z_travel_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.z_travel_entry.set_range(0.0000001, 9999.9999)
+        self.z_travel_entry.set_range(0.0000001, 10000.0000)
         self.z_travel_entry.set_precision(self.decimals)
         self.z_travel_entry.setSingleStep(0.1)
 
@@ -1297,7 +1299,7 @@ class SolderUI:
 
         # Z toolchange location
         self.z_toolchange_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.z_toolchange_entry.set_range(0.0000001, 9999.9999)
+        self.z_toolchange_entry.set_range(0.0000001, 10000.0000)
         self.z_toolchange_entry.set_precision(self.decimals)
         self.z_toolchange_entry.setSingleStep(0.1)
 
@@ -1318,7 +1320,7 @@ class SolderUI:
 
         # Feedrate X-Y
         self.frxy_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.frxy_entry.set_range(0.0000, 99999.9999)
+        self.frxy_entry.set_range(0.0000, 910000.0000)
         self.frxy_entry.set_precision(self.decimals)
         self.frxy_entry.setSingleStep(0.1)
 
@@ -1330,7 +1332,7 @@ class SolderUI:
 
         # Feedrate Z
         self.frz_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.frz_entry.set_range(0.0000, 99999.9999)
+        self.frz_entry.set_range(0.0000, 910000.0000)
         self.frz_entry.set_precision(self.decimals)
         self.frz_entry.setSingleStep(0.1)
 
@@ -1343,14 +1345,14 @@ class SolderUI:
 
         # Feedrate Z Dispense
         self.frz_dispense_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.frz_dispense_entry.set_range(0.0000, 99999.9999)
+        self.frz_dispense_entry.set_range(0.0000, 910000.0000)
         self.frz_dispense_entry.set_precision(self.decimals)
         self.frz_dispense_entry.setSingleStep(0.1)
 
         self.frz_dispense_label = QtWidgets.QLabel('%s:' % _("Feedrate Z Dispense"))
         self.frz_dispense_label.setToolTip(
             _("Feedrate (speed) while moving up vertically\n"
-              " to Dispense position (on Z plane).")
+              "to Dispense position (on Z plane).")
         )
         self.gcode_form_layout.addRow(self.frz_dispense_label, self.frz_dispense_entry)
 
@@ -1368,7 +1370,7 @@ class SolderUI:
 
         # Dwell Forward
         self.dwellfwd_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.dwellfwd_entry.set_range(0.0000001, 9999.9999)
+        self.dwellfwd_entry.set_range(0.0000001, 10000.0000)
         self.dwellfwd_entry.set_precision(self.decimals)
         self.dwellfwd_entry.setSingleStep(0.1)
 
@@ -1392,7 +1394,7 @@ class SolderUI:
 
         # Dwell Reverse
         self.dwellrev_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.dwellrev_entry.set_range(0.0000001, 9999.9999)
+        self.dwellrev_entry.set_range(0.0000001, 10000.0000)
         self.dwellrev_entry.set_precision(self.decimals)
         self.dwellrev_entry.setSingleStep(0.1)
 

Some files were not shown because too many files changed in this diff